1. 소켓

소켓(Socket)은 네트워크를 통해 두 컴퓨터가 데이터를 주고받기 위한 통신의 출입구(Endpoint) 역할을 하는 소프트웨어 인터페이스입니다. 서버와 클라이언트가 소켓을 통해 연결되면, 마치 전화선을 통해 대화하듯이 양방향으로 데이터를 주고받을 수 있는 통신 통로가 형성됩니다. 자바나 파이썬 등 여러 프로그래밍 언어에서는 소켓 라이브러리를 제공하며, 이를 활용하면 TCP/IP 또는 UDP 방식의 네트워크 프로그램을 구현할 수 있습니다. 예를 들어 채팅 프로그램, 게임 서버, 실시간 데이터 송수신 시스템 등이 모두 소켓을 기반으로 동작합니다. 브라우저 지원 후 웹 소켓으로 챗을 만들 때 사용한다.

 

1.1 tcp/ip

TCP/IP는 인터넷을 포함한 대부분의 네트워크에서 사용되는 표준 통신 프로토콜 체계로, 데이터를 정확하고 안정적으로 주고받기 위한 규칙들의 집합입니다. TCP(Transmission Control Protocol)는 데이터의 신뢰성과 순서를 보장하며, IP(Internet Protocol)는 데이터를 목적지까지 전달하는 역할을 합니다. TCP는 데이터를 작은 조각(패킷)으로 나누고, 수신자가 이를 정확하게 재조립하도록 도와주며, 전송 중 손실되면 재전송을 요청합니다. IP는 이러한 패킷들이 인터넷을 통해 올바른 주소로 전달되도록 합니다. 두 프로토콜이 함께 동작하면서, 웹 브라우징, 이메일, 파일 전송 등 인터넷 기반 서비스가 가능해집니다.

 

1.2 UDP

UDP(User Datagram Protocol)는 TCP와 달리 데이터의 전송 순서나 도착 여부를 보장하지 않는 비연결형 통신 프로토콜입니다. 빠른 속도가 중요한 실시간 통신에서 주로 사용되며, TCP보다 구조가 간단하고 오버헤드가 적어 전송 지연이 매우 짧습니다. 하지만 오류 검사나 재전송 같은 기능이 없기 때문에 데이터가 유실되거나 순서가 바뀔 수도 있습니다. 대표적인 사용 사례로는 실시간 영상 스트리밍, 온라인 게임, 음성 통화(VoIP) 등이 있으며, 이들 서비스는 약간의 데이터 손실보다 빠른 전송을 더 중요하게 생각합니다.

 

 

2. 소켓 통신

자바의 소켓 통신은 Socket 클래스와 ServerSocket 클래스를 이용하여 클라이언트와 서버가 네트워크를 통해 데이터를 주고받을 수 있도록 해줍니다. Java에서는 기본적으로 TCP 기반의 소켓 통신을 제공합니다.

 

1. 서버

  • ServerSocket을 열고 특정 포트에서 클라이언트의 연결 요청을 대기함.
  • accept() 메서드로 클라이언트가 접속하면 Socket 객체를 생성하여 통신을 시작함.

 

2. 클라이언트

  • Socket을 사용하여 서버의 IP와 포트를 통해 서버에 연결 요청을 보냄.
  • 연결이 성공되면 서버와 데이터를 주고받을 수 있음.
  • 서버/클라이언트 양쪽 모두 InputStream과 OutputStream을 이용해 문자열, 숫자 등의 데이터를 송수신함.
package lesson09;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Ex01_Server_Main {
    public static void main(String[] args) {
        int port = 12345;

        try(ServerSocket serverSocket = new ServerSocket(port)) {
            System.out.println("서버 대기 중...");
            // 클라이언트가 대기하는 메소드
            Socket clientSocket = serverSocket.accept();
            System.out.println("클라이언트 연결됨: "+clientSocket.getInetAddress());
            // Buffer : 하드웨어에 있는 기술 중 하나로 저장할 수 있는 또다른 메모리 공강
            //        : 버퍼에 데이터가 안정적으로 쌓이게 되면 사용자에게 전송 즉, 임시저장느낌
            BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            // autoFlush : 자동으로 클라이언트 쪽으로 나가는 기능, buffer --> flush
            PrintWriter out = new PrintWriter(clientSocket.getOutputStream(),true);

            String inpiut  = in.readLine();
            System.out.println("클라이언트로부터 받은 메세지: "+inpiut);

            // 받자마자 바로 전송
            out.println(inpiut);
            clientSocket.close();
        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

 

package lesson09;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Ex01_Client_Main {
    public static void main(String[] args) {
        String severIp = "127.0.0.1";
        int port = 12345;

        try(Socket socket = new Socket(severIp, port)) {
            System.out.println("서버에 연결됨");

            // 서버와 클라이언트 서버가 연결되는 구조임
            // 데이터 입출력 스트림
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            out.println("안녕하세요! 서버!");

            String response = in.readLine();
            System.out.println("서버 응답: " + response);

        }catch(IOException e){
            e.printStackTrace();
        }
    }
}

 

 

3. 다중 클라이언트 채팅

- ChatServer

package lesson09;
// 서버 역할

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

public class Ex02_ChatServer {
    private static final int PORT = 12345;
    // concurrentHashMap : thread에서 안전하게 동작하는 자료구조
    private static final Set<Ex02_ClientHandler> clients = ConcurrentHashMap.newKeySet();

    public static void main(String[] args) {
        System.out.println("채팅 서버 시작...");
        try(ServerSocket serverSocket = new ServerSocket(PORT)) {
            while(true) {
                Socket socket = serverSocket.accept();
                System.out.println("클라이언트 연결됨: "+socket.getInetAddress());
                // 핸들러 객체 생성 + socket
                Ex02_ClientHandler handler = new Ex02_ClientHandler(socket);
                // 사용자 한명 추가
                clients.add(handler);
                // 사용자가 활동
                new Thread(handler).start();
            }
        }catch(IOException e){
            e.printStackTrace();
        }
    }
    // (채팅글, 보낸이 )
    public static void broadcast(String message,Ex02_ClientHandler sender){
        for(Ex02_ClientHandler client : clients){
            // 남들에게만 보내는
            if(client !=sender){
                // sendMessage 는 핸들러 안에서 생성하면 됨.
                client.sendMessage(message);
            }
        }
    }

    public static void removeClient(Ex02_ClientHandler client){
        clients.remove(client);

    }
}

 

 

- ClientHandler

package lesson09;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Ex02_ClientHandler implements Runnable {
    private Socket socket;
    private PrintWriter out;
    private BufferedReader in;

    public Ex02_ClientHandler(Socket socket) {
        this.socket = socket;
        try {
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            out.println("서버에 연결되었습니다. 닉네임을 입력하세요: ");
        }catch (IOException e){
            e.printStackTrace();
        }
    }

    public void sendMessage(String msg){
        out.println(msg);
    }

    @Override
    public void run() {
        try {
            String name = in.readLine();
            Ex02_ChatServer.broadcast(name + "님이 입장하셨습니다.", this);

            String message;
            while ((message = in.readLine()) != null){
                if(message.equalsIgnoreCase("exit")) break;
                Ex02_ChatServer.broadcast(name + ": " + message, this);
            }
            out.println("채팅을 종료합니다.");
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                Ex02_ChatServer.removeClient(this);
                socket.close();
            }catch (IOException e){
                e.printStackTrace();
            }
        }
    }
}

 

- ChatClient

package lesson09;
//


import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class Ex02_ChatClient {

    public static void main(String[] args) {
        String serverIP = "127.0.0.1";
        int port = 12345;

        try (Socket socket = new Socket(serverIP, port)) {
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            // 키보드 입력
            BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(socket.getOutputStream(),true);

            new Thread(() -> {
                String serverMsg;
                try{
                    while ((serverMsg = in.readLine()) != null){
                        System.out.println(serverMsg);
                    }
                }catch (IOException e){
                    System.out.println("서버 연결 종료");
                }
            }).start();

            String input;
            while ((input = keyboard.readLine()) != null){
                out.println(input);
                if(input.equalsIgnoreCase("exit"))break;
            }

        }catch (IOException e){
            e.printStackTrace();
        }
    }
}

 

 

 

4. 파일 전송

1. 바이너리 데이터 전송

  • 문자열 전송과 달리 파일은 바이너리 데이터(0과 1)입니다.
  • 텍스트 전송에 쓰이는 BufferedReader, PrintWriter는 텍스트 전용 스트림이므로 파일 전송에는 InputStream과 OutputStream을 사용해야 합니다.

 

2. 파일 크기 및 이름 전달

  • 파일 전송 전에는 수신자가 파일의 이름, 크기 등을 알아야 저장할 수 있습니다.
  • 따라서 파일 전송은 일반적으로 다음과 같은 순서로 진행됩니다.
1. 파일 전송 명령 전송 (예: /file filename.jpg)
2. 서버가 수신 준비
3. 클라이언트가 파일 크기와 이름을 먼저 보냄
4. 이후 실제 파일 바이너리 전송

 

3. 스레드 분리 또는 프로토콜 구분

  • 기존 채팅 메시지와 파일 전송 데이터를 구분하기 위한 간단한 프로토콜(명령 구분자)이 필요합니다.
  • 예: 채팅 메시지는 그냥 보내고, 파일은 /file filename 명령으로 구분

 

다중 클라이언트 채팅 (파일 전송 기능 추가)

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.concurrent.*;

public class ChatServer {
    private static final int PORT = 12345;
    private static final Set<ClientHandler> clients = ConcurrentHashMap.newKeySet();

    public static void main(String[] args) {
        System.out.println("채팅 서버 시작...");

        try (ServerSocket serverSocket = new ServerSocket(PORT)) {
            while (true) {
                Socket socket = serverSocket.accept();
                ClientHandler handler = new ClientHandler(socket);
                clients.add(handler);
                new Thread(handler).start();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void broadcast(String message, ClientHandler sender) {
        for (ClientHandler client : clients) {
            if (client != sender) {
                client.sendMessage(message);
            }
        }
    }

    public static void removeClient(ClientHandler client) {
        clients.remove(client);
    }
}

 

import java.io.*;
import java.net.*;

public class ClientHandler implements Runnable {
    private Socket socket;
    private BufferedReader in;
    private PrintWriter out;

    public ClientHandler(Socket socket) {
        this.socket = socket;
        try {
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);
            out.println("서버에 연결되었습니다. 닉네임을 입력하세요:");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void sendMessage(String message) {
        out.println(message);
    }

    private File getUniqueFile(String baseName) {
        File file = new File("server_" + baseName);
        int count = 1;
        while (file.exists()) {
            String name = baseName;
            int dotIndex = name.lastIndexOf('.');
            if (dotIndex != -1) {
                name = name.substring(0, dotIndex) + "(" + count + ")" + name.substring(dotIndex);
            } else {
                name = name + "(" + count + ")";
            }
            file = new File("server_" + name);
            count++;
        }
        return file;
    }

    private void receiveFile(String fileName) {
        try {
            DataInputStream dis = new DataInputStream(socket.getInputStream());

            long fileSize = dis.readLong();
            File file = getUniqueFile(fileName);
            FileOutputStream fos = new FileOutputStream(file);

            byte[] buffer = new byte[4096];
            long totalRead = 0;
            int read, percent = 0;

            System.out.println("파일 수신 시작: " + file.getName() + " (" + fileSize + " bytes)");

            while (totalRead < fileSize && (read = dis.read(buffer)) != -1) {
                fos.write(buffer, 0, read);
                totalRead += read;
                int newPercent = (int)((totalRead * 100) / fileSize);
                if (newPercent > percent) {
                    percent = newPercent;
                    System.out.print("\r서버 수신 진행률: " + percent + "%");
                }
            }

            System.out.println("\n파일 저장 완료: " + file.getName());
            fos.close();
            ChatServer.broadcast("파일 [" + file.getName() + "] 수신 완료", this);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        try {
            String name = in.readLine();
            ChatServer.broadcast(name + "님이 입장하셨습니다.", this);

            String message;
            while ((message = in.readLine()) != null) {
                if (message.startsWith("/file ")) {
                    String fileName = message.substring(6);
                    receiveFile(fileName);
                } else if (message.equalsIgnoreCase("exit")) {
                    break;
                } else {
                    ChatServer.broadcast(name + ": " + message, this);
                }
            }

            out.println("채팅을 종료합니다.");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                ChatServer.removeClient(this);
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

 

import java.io.*;
import java.net.*;

public class ChatClient {
    public static void main(String[] args) {
        String serverIP = "127.0.0.1";
        int port = 12345;

        try (Socket socket = new Socket(serverIP, port)) {
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
            PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

            // 서버 메시지 수신 스레드
            new Thread(() -> {
                try {
                    String line;
                    while ((line = in.readLine()) != null) {
                        System.out.println(line);

                        if (line.startsWith("파일 [") && line.endsWith("] 수신 완료")) {
                            String fileName = line.substring(4, line.indexOf("]"));
                            receiveFile(socket, fileName);
                        }
                    }
                } catch (IOException e) {
                    System.out.println("서버 연결 종료");
                }
            }).start();

            // 키보드 입력 처리
            String input;
            while ((input = keyboard.readLine()) != null) {
                if (input.startsWith("/file ")) {
                    String filePath = input.substring(6);
                    sendFile(socket, filePath, out);
                } else {
                    out.println(input);
                    if (input.equalsIgnoreCase("exit")) break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void sendFile(Socket socket, String filePath, PrintWriter out) {
        try {
            File file = new File(filePath);
            long fileSize = file.length();
            String fileName = file.getName();

            out.println("/file " + fileName);

            DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
            FileInputStream fis = new FileInputStream(file);

            dos.writeLong(fileSize);

            byte[] buffer = new byte[4096];
            long totalSent = 0;
            int read, percent = 0;

            System.out.println("파일 전송 시작: " + fileName + " (" + fileSize + " bytes)");

            while ((read = fis.read(buffer)) != -1) {
                dos.write(buffer, 0, read);
                totalSent += read;
                int newPercent = (int)((totalSent * 100) / fileSize);
                if (newPercent > percent) {
                    percent = newPercent;
                    System.out.print("\r클라이언트 전송 진행률: " + percent + "%");
                }
            }

            fis.close();
            dos.flush();
            System.out.println("\n파일 전송 완료: " + fileName);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static File getUniqueFile(String baseName) {
        File file = new File("client_" + baseName);
        int count = 1;
        while (file.exists()) {
            String name = baseName;
            int dotIndex = name.lastIndexOf('.');
            if (dotIndex != -1) {
                name = name.substring(0, dotIndex) + "(" + count + ")" + name.substring(dotIndex);
            } else {
                name = name + "(" + count + ")";
            }
            file = new File("client_" + name);
            count++;
        }
        return file;
    }

    private static void receiveFile(Socket socket, String fileName) {
        try {
            DataInputStream dis = new DataInputStream(socket.getInputStream());
            long fileSize = dis.readLong();
            File file = getUniqueFile(fileName);
            FileOutputStream fos = new FileOutputStream(file);

            byte[] buffer = new byte[4096];
            long totalRead = 0;
            int read, percent = 0;

            System.out.println("파일 수신 시작: " + file.getName() + " (" + fileSize + " bytes)");

            while (totalRead < fileSize && (read = dis.read(buffer)) != -1) {
                fos.write(buffer, 0, read);
                totalRead += read;
                int newPercent = (int)((totalRead * 100) / fileSize);
                if (newPercent > percent) {
                    percent = newPercent;
                    System.out.print("\r클라이언트 수신 진행률: " + percent + "%");
                }
            }

            fos.close();
            System.out.println("\n파일 저장 완료: " + file.getName());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

'Server > Java' 카테고리의 다른 글

JDBC  (0) 2025.05.30
Thread  (0) 2025.05.29
DTO와 VO  (0) 2025.05.28
Generic  (0) 2025.05.27
java.lang package  (0) 2025.05.27

1. JDBC

JDBC(Java Database Connectivity)는 자바에서 데이터베이스에 접근하고 SQL을 실행할 수 있도록 지원하는 표준 API(인터페이스)입니다. 이를 통해 자바 애플리케이션은 MySQL, Oracle, PostgreSQL 등 다양한 관계형 데이터베이스에 연결하여 데이터를 조회하거나 추가, 수정, 삭제할 수 있습니다. JDBC는 데이터베이스별로 제공되는 드라이버를 통해 실제 연결을 처리하며, 주요 구성 요소로는 데이터베이스와의 연결을 관리하는 Connection, SQL을 실행하는 Statement 또는 PreparedStatement, 결과를 처리하는 ResultSet 등이 있습니다. JDBC는 자바와 DB 간의 중간 다리 역할을 하며, 자바 애플리케이션에서 데이터베이스를 활용한 기능을 개발하는 데 필수적인 기술입니다.

 

2. MySQL과의 연동

1. MySQL 설치

  • MySQL 서버 설치 (Windows/Mac/Linux 중 선택)
  • DB 사용자 계정 및 비밀번호 확인
  • 예: user: root, password: 1234

 

2. JDBC 드라이버 설치

  • MySQL Connector/J를 다운로드하거나,
  • Maven/Gradle을 사용할 경우 자동으로 라이브러리를 추가 가능
  • JDBC 드라이버 의존성 추가 (Maven 기준)
<dependencies>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.33</version>
    </dependency>
</dependencies>

 

Maven

Maven은 자바 프로젝트의 빌드(build), 의존성 관리(dependency management), 프로젝트 구조화 등을 자동으로 처리해주는 빌드 도구(Build Tool)입니다. 개발자가 필요한 외부 라이브러리를 일일이 다운로드하지 않고, pom.xml 파일에 필요한 라이브러리를 선언만 하면 Maven이 자동으로 인터넷에서 해당 JAR 파일을 받아 프로젝트에 추가해줍니다. 또한 프로젝트 컴파일, 테스트, 패키징(JAR/war 파일 생성)까지 일괄적으로 실행할 수 있어 일관된 개발 환경을 유지하고 협업을 쉽게 만들어주는 강력한 도구입니다. Maven은 특히 대규모 프로젝트나 팀 개발에서 널리 사용되며, 대부분의 현대 자바 개발 환경에서는 표준처럼 사용됩니다.

 

Gradle ⭐⭐

Gradle은 자바를 포함한 다양한 언어의 프로젝트에서 빌드, 의존성 관리, 배포 등을 자동화해주는 현대적인 빌드 도구(Build Tool)입니다. 기존의 Maven보다 더 빠르고 유연하며, Groovy 또는 Kotlin DSL을 사용해 스크립트 형식으로 빌드 과정을 직접 제어할 수 있는 것이 특징입니다. build.gradle 파일에 필요한 라이브러리와 작업을 선언하면, Gradle이 자동으로 필요한 라이브러리를 다운로드하고 컴파일, 테스트, 패키징 등을 수행합니다. 특히 속도와 커스터마이징 유연성이 뛰어나서, Android 개발이나 복잡한 빌드 환경에서 널리 사용됩니다. Maven과 달리 코드처럼 작성할 수 있어 자동화와 확장이 쉬운 빌드 도구로 인식되고 있습니다.

 

 

3. Statement

Statement는 JDBC API에서 제공하는 인터페이스로, SQL 문장을 데이터베이스에 전달하고 실행 결과를 받아오는 데 사용됩니다. Statement는 사용자 입력값이 직접 SQL 문자열에 들어가기 때문에 보안에 취약하며,  가독성이 떨어지고 반복되는 코드는 유지보수가 어렵다는 단점이 있습니다. 

- 유지보수 & 가독성면에서 단점이 존재함.

 

cf. JDBC API : 관계형 데이터베이스의 select, delete, search etc...클래스를 사용할 수 있게 만들어 놓음. 단, driver 따로 설치 해야함.

executeQuery(String sql) SELECT 문 실행 – 결과는 ResultSet 객체로 반환 
// x,y 구조로 대용량 데이터를 반환하기 때문에 따로 실행
executeUpdate(String sql) INSERT, UPDATE, DELETE – 영향을 받은 행 수(int) 반환
execute(String sql) 모든 SQL 문 실행 가능 – 실행 결과에 따라 ResultSet 또는 boolean 반환

 

MySQL에서 테이블 생성

create table words (
	eng varchar(50) primary key,
    kor varchar(50) not null,
    lev int default 1,
    regdate datetime default now()
);

 

import java.sql.*;

public class JdbcEx {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/aiproject?serverTimezone=Asia/Seoul";
        String user = "root";
        String password = "1234";

        try {
            // 1. 드라이버 로드
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. DB 연결
            Connection conn = DriverManager.getConnection(url, user, password);
            System.out.println("MySQL 연결 성공!");

            // 3. 쿼리 실행
            Statement stmt = conn.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT * FROM words");

            while (rs.next()) {
                String eng = rs.getString("eng");
                String kor = rs.getString("kor");
                int lev = rs.getInt("lev");
                System.out.println(eng + ": " + kor + "(레벨: " + lev + ")");
            }

            // 4. 자원 정리
            rs.close();
            stmt.close();
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

 

4. PreparedStatement

PreparedStatement는 JDBC에서 SQL 문장을 미리 준비하고 실행할 수 있도록 해주는 객체로, Statement보다 보안과 성능 측면에서 훨씬 우수합니다.

SELECT * FROM users WHERE name = ?

 

  • ? 부분은 바인딩 파라미터
  • 실행 시 자바 코드에서 값을 넣음 (pstmt.setString(1, "김사과");)
setString(int index, String value) 문자열 설정
setInt(int index, int value) 정수 설정
setDate(int index, java.sql.Date) 날짜 설정
executeQuery() SELECT 실행
executeUpdate() INSERT/UPDATE/DELETE 실행
close() 자원 정리

 

import java.sql.*;

public class JdbcEx {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/nodejs?serverTimezone=Asia/Seoul";
        String user = "root";
        String password = "1234";

        try {
            // 1. 드라이버 로드
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. DB 연결
            Connection conn = DriverManager.getConnection(url, user, password);
            System.out.println("MySQL 연결 성공!");

            // 3. PreparedStatement 생성
            String sql = "SELECT * FROM words WHERE lev >= ?";
            PreparedStatement pstmt = conn.prepareStatement(sql);
            pstmt.setInt(1, 2);  // 예: 레벨 2 이상인 단어만 조회

            // 4. 쿼리 실행
            ResultSet rs = pstmt.executeQuery();

            while (rs.next()) {
                String eng = rs.getString("eng");
                String kor = rs.getString("kor");
                int lev = rs.getInt("lev");
                System.out.println(eng + ": " + kor + " (레벨: " + lev + ")");
            }

            // 5. 자원 정리
            rs.close();
            pstmt.close();
            conn.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

 

import java.sql.*;

public class InsertEx {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/nodejs?serverTimezone=Asia/Seoul";
        String user = "root";
        String password = "1234";

        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            // 1. 드라이버 로드
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. DB 연결
            conn = DriverManager.getConnection(url, user, password);
            System.out.println("MySQL 연결 성공!");

            // 3. INSERT 쿼리 준비
            String sql = "INSERT INTO words(eng, kor, lev) VALUES(?, ?, ?)";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, "apple");     // 영단어
            pstmt.setString(2, "사과");       // 한글뜻
            pstmt.setInt   (3, 2);           // 레벨

            // 4. 쿼리 실행
            int count = pstmt.executeUpdate();
            System.out.println(count + "개 행이 삽입되었습니다.");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 자원 정리
            try { if (pstmt != null) pstmt.close(); } catch (SQLException e) { e.printStackTrace(); }
            try { if (conn  != null) conn.close();  } catch (SQLException e) { e.printStackTrace(); }
        }
    }
}

 

import java.sql.*;

public class UpdateEx {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/nodejs?serverTimezone=Asia/Seoul";
        String user = "root";
        String password = "1234";

        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            // 1. 드라이버 로드
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. DB 연결
            conn = DriverManager.getConnection(url, user, password);
            System.out.println("MySQL 연결 성공!");

            // 3. UPDATE 쿼리 준비
            String sql = "UPDATE words SET kor = ?, lev = ? WHERE eng = ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, "애플");     // 새로운 한글뜻
            pstmt.setInt   (2, 3);         // 새로운 레벨
            pstmt.setString(3, "apple");   // 조건: eng 컬럼

            // 4. 쿼리 실행
            int count = pstmt.executeUpdate();
            System.out.println(count + "개 행이 수정되었습니다.");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 자원 정리
            try { if (pstmt != null) pstmt.close(); } catch (SQLException e) { e.printStackTrace(); }
            try { if (conn  != null) conn.close();  } catch (SQLException e) { e.printStackTrace(); }
        }
    }
}

 

import java.sql.*;

public class DeleteEx {
    public static void main(String[] args) {
        String url = "jdbc:mysql://localhost:3306/nodejs?serverTimezone=Asia/Seoul";
        String user = "root";
        String password = "1234";

        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            // 1. 드라이버 로드
            Class.forName("com.mysql.cj.jdbc.Driver");

            // 2. DB 연결
            conn = DriverManager.getConnection(url, user, password);
            System.out.println("MySQL 연결 성공!");

            // 3. DELETE 쿼리 준비
            String sql = "DELETE FROM words WHERE eng = ?";
            pstmt = conn.prepareStatement(sql);
            pstmt.setString(1, "apple");   // 삭제할 영단어

            // 4. 쿼리 실행
            int count = pstmt.executeUpdate();
            System.out.println(count + "개 행이 삭제되었습니다.");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 5. 자원 정리
            try { if (pstmt != null) pstmt.close(); } catch (SQLException e) { e.printStackTrace(); }
            try { if (conn  != null) conn.close();  } catch (SQLException e) { e.printStackTrace(); }
        }
    }
}

 

 

5. try-with-resources

Java 7부터 도입된 자원 자동 해제 기능으로, try 구문 안에서 선언한 AutoCloseable(또는 Closeable) 타입의 자원을 블록이 끝날 때 자동으로 close() 해 줍니다. 수동으로 finally 블록을 작성할 필요 없이 깔끔하고 안전하게 자원을 관리할 수 있습니다.

try (리소스 선언 및 초기화) {
    // 자원을 사용하는 코드
} catch (예외타입 e) {
    // 예외 처리
}

 

  • try 괄호 안에 여러 개의 자원을 세미콜론(;)으로 구분해 나열할 수 있습니다.
  • 블록을 벗어날 때, 선언된 자원들의 close()가 역순으로 자동 호출됩니다.
  • 자원이 null이 아닌 경우에만 close()를 호출합니다.
import java.sql.*;

public class SelectWithResources {
    public static void main(String[] args) {
        String url      = "jdbc:mysql://localhost:3306/nodejs?serverTimezone=Asia/Seoul";
        String user     = "root";
        String password = "1234";

        String sql = "SELECT * FROM words WHERE lev >= ?";

        // try(...) 안에서 선언된 자원은 자동으로 close()
        try (
            Connection        conn  = DriverManager.getConnection(url, user, password);
            PreparedStatement pstmt = conn.prepareStatement(sql)
        ) {
            Class.forName("com.mysql.cj.jdbc.Driver");
            pstmt.setInt(1, 2);

            // ResultSet 또한 try-with-resources에 포함 가능
            try (ResultSet rs = pstmt.executeQuery()) {
                while (rs.next()) {
                    String eng = rs.getString("eng");
                    String kor = rs.getString("kor");
                    int lev    = rs.getInt("lev");
                    System.out.println(eng + ": " + kor + " (레벨: " + lev + ")");
                }
            }
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
            // 필요시 e.getSuppressed()로 억제된 예외 확인 가능
        }
    }
}

 

 

  • Connection, PreparedStatement, ResultSet 모두 AutoCloseable을 구현하므로 try(...)에 나열할 수 있습니다.
  • 리소스 해제 코드가 전혀 없기 때문에 가독성이 월등히 좋아집니다.

'Server > Java' 카테고리의 다른 글

Socket  (0) 2025.06.02
Thread  (0) 2025.05.29
DTO와 VO  (0) 2025.05.28
Generic  (0) 2025.05.27
java.lang package  (0) 2025.05.27

1. 스레드

스레드(Thread)는 하나의 프로세스 내에서 독립적으로 실행되는 실행 흐름 단위를 말합니다. 일반적으로 하나의 프로그램(프로세스)은 하나 이상의 스레드를 가질 수 있으며, 이를 통해 동시에 여러 작업을 처리하는 멀티스레딩(Multithreading)이 가능합니다. 예를 들어 음악을 재생하면서 동시에 파일을 다운로드하거나 사용자 입력을 처리하는 등의 작업을 병렬로 수행할 수 있습니다. 자바에서는 Thread 클래스를 상속하거나 Runnable 인터페이스를 구현하여 스레드를 정의하고 실행할 수 있으며, 효율적인 CPU 자원 활용과 반응성 향상에 유리하지만, 스레드 간 자원 공유로 인해 동기화(synchronization)와 같은 주의가 필요합니다.

 

 

2. 스레드 생성

1. Thread 클래스를 상속하는 방식

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("스레드 실행 중! - " + Thread.currentThread().getName());
    }
}

public class ThreadExample1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();
        t1.start();  // run()이 아닌 start()를 호출해야 새 스레드가 생성됨
    }
}

 

2. Runnable 인터페이스 구현 방식

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("Runnable 스레드 실행 중! - " + Thread.currentThread().getName());
    }
}

public class ThreadExample2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyRunnable());
        t1.start();
    }
}

 

3. 익명 객체로 스레드 생성하는 방법

public class AnonymousThreadExample1 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("익명 Thread 클래스 실행!");
            }
        };
        t.start();
    }
}

 

public class AnonymousThreadExample2 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("익명 Runnable 객체 실행!");
            }
        });
        t.start();
    }
}

 

public class LambdaThreadExample {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("람다식으로 만든 스레드 실행!");
        });
        t.start();
    }
}

 

 

3. 스레드 병렬 실행

class PrintTask implements Runnable {
    private String message;

    public PrintTask(String message) {
        this.message = message;
    }

    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(message + " - " + i);
        }
    }
}

public class MultiThreadTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(new PrintTask("🍎 김사과"));
        Thread t2 = new Thread(new PrintTask("🍌 반하나"));

        t1.start();
        t2.start();
    }
}

 

 

4. 스레드 동기화 (synchronized)

여러 스레드가 공유 자원을 동시에 수정하면 충돌이 발생할 수 있습니다. synchronized 키워드는 한 번에 한 스레드만 메서드에 접근하도록 합니다.

package lesson08;

class Ex10_Counter{
    private int count=0;

    public void increment(){
        count ++;
    }
    public int getCount(){
        return count;
    }
}

public class Ex10_Main {
    public static void main(String[] args) throws InterruptedException {
        Ex10_Counter counter = new Ex10_Counter();
        Runnable task = ()->{
            for(int i = 0;i<10000;i++){
                counter.increment();
            }
        };
        Thread th1 = new Thread(task);
        Thread th2 = new Thread(task);
        th1.start();
        th2.start();
        // 프로그램 끝날 때까지 join 으로 계속 실행시킴
        // 오류가 나면 main 에다가 throw InterruptedException을 날리면 해결됨
        th1.join();
        th2.join();
        System.out.println("최종 카운트: "+counter.getCount());
        // 최종 결과가 2만이 나와야하는데 나오지 않는 이유는 method가 동시에 불려지면서 카운트할 때, 싱크가 맞지 않아서 이다.

    }
}
package lesson08;

class Ex10_Counter{
    private int count=0;

	// 여기에 싱크를 걸면 우리가 예상했던 대로 2만이 나온다.
    public synchronized void increment(){
        count ++;
    }
    public int getCount(){
        return count;
    }
}

1. 메서드 전체 동기화

public synchronized void methodName() {
    // 이 메서드는 한 번에 하나의 스레드만 실행 가능
}

 

2. 특정 블록만 동기화

  • 메서드 전체를 동기화하지 않고, 필요한 부분만 동기화 가능
  • 성능 최적화에 좋음
public void methodName() {
    synchronized (this) {
        // this: 현재 인스턴스를 lock
    }
}

// methodName에 싱크를 걸게되면 효율이 낮아짐.

 

class SafeCounter {
    private int count = 0;

    public synchronized void increment() {
        count++;  // 메서드에 synchronized → 한 번에 하나의 스레드만 실행
    }

    public int getCount() {
        return count;
    }
}

public class SyncExample {
    public static void main(String[] args) throws InterruptedException {
        SafeCounter counter = new SafeCounter();

        Runnable task = () -> {
            for (int i = 0; i < 10000; i++) {
                counter.increment();  // 안전하게 동작
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);
        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("최종 카운트: " + counter.getCount());  // 항상 20000
    }
}

 

join()

Thread.join() 메서드는 다른 스레드가 종료될 때까지 현재 스레드(보통 main 스레드)가 기다리게 만드는 메서드입니다. 즉, t1.join();은 현재 실행 중인 스레드(main 등)가 t1이 끝날 때까지 멈춰서 기다리도록 강제합니다.

 

 

5. ThreadPool

스레드 풀(Thread Pool)은 미리 생성된 스레드들을 풀(pool)에 담아두고, 작업이 생기면 재사용하는 방식입니다. 매번 새 스레드를 생성하는 것이 아니라 재사용하여 성능을 향상시키고 자원 낭비를 줄입니다.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3); // 최대 3개 스레드 사용

        for (int i = 1; i <= 5; i++) {
            int taskId = i;
            executor.submit(() -> {
                System.out.println("작업 " + taskId + "을 실행 중 (스레드: " + Thread.currentThread().getName() + ")");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {}
                System.out.println("작업 " + taskId + " 완료");
            });
        }

        executor.shutdown(); // 작업 제출은 종료 (스레드가 자동 종료되도록)
    }
}

 

 

6. Callable & Future

  • Runnable은 반환값이 없는 작업만 가능
  • Callable<T>은 반환값을 가질 수 있는 작업을 표현(return 값을 반환)
  • Future<T>는 Callable 작업의 결과를 나중에 비동기적으로 받아올 수 있는 객체(return 값을 처리)
import java.util.concurrent.*;

public class CallableFutureExample {
    public static void main(String[] args) throws Exception {
        ExecutorService executor = Executors.newSingleThreadExecutor();

        Callable<Integer> task = () -> {
            System.out.println("복잡한 계산 중...");
            Thread.sleep(2000); // 2초 지연
            return 42; // 결과값 반환
        };

        Future<Integer> future = executor.submit(task);

        System.out.println("메인 스레드는 다른 작업 중...");
        Integer result = future.get();  // 결과가 준비될 때까지 대기

        System.out.println("계산 결과: " + result);
        executor.shutdown();
    }
}

 

 

  • submit() → Callable 작업을 실행하고 결과를 Future로 감싸 반환
  • future.get() → 결과가 나올 때까지 대기
  • 비동기 실행이 가능하며, 필요할 때만 결과를 요청

 

 

7. invokeAll() & invokeAny()

1. invokeAll()

 

여러 Callable<T> 작업을 한꺼번에 제출하고, 모든 작업이 완료될 때까지 대기한 뒤 각 작업의 결과를 List<Future<T>>로 반환합니다.

 

 

  • 블록킹: 모든 작업이 종료될 때까지 호출 스레드는 return을 기다립니다.
  • 반환된 Future들을 순회하며 get()을 호출하면 예외 없이 즉시 결과를 얻을 수 있습니다.
  • 타임아웃을 지정할 수도 있습니다(invokeAll(tasks, timeout, unit)).

 

import java.util.*;
import java.util.concurrent.*;

public class InvokeAllExample {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        // 3개의 Callable 작업 생성
        List<Callable<String>> tasks = List.of(
            () -> { Thread.sleep(500); return "🍎 사과"; },
            () -> { Thread.sleep(300); return "🍌 바나나"; },
            () -> { Thread.sleep(700); return "🍇 포도"; }
        );

        // 모든 작업이 끝날 때까지 대기
        List<Future<String>> futures = executor.invokeAll(tasks);

        // 결과 출력
        for (Future<String> f : futures) {
            try {
                System.out.println("결과: " + f.get());
            } catch (ExecutionException e) {
                System.out.println("작업 중 예외 발생: " + e.getCause());
            }
        }

        executor.shutdown();
    }
}

 

2. invokeAny()

여러 Callable<T> 작업을 제출하고, 가장 먼저 완료된(성공적으로 반환된) 하나의 결과만 리턴합니다. 나머지 작업은 취소됩니다.

 

  • 가장 빠른 결과만 필요할 때 효율적
  • 블록킹: 최초 성공 작업이 종료될 때까지 대기
  • 타임아웃을 지정할 수도 있습니다(invokeAny(tasks, timeout, unit)).
import java.util.*;
import java.util.concurrent.*;

public class InvokeAnyExample {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(3);

        List<Callable<String>> tasks = List.of(
            () -> { Thread.sleep(500); return "🍎 사과"; },
            () -> { Thread.sleep(300); return "🍌 바나나"; },
            () -> { Thread.sleep(700); return "🍇 포도"; }
        );

        try {
            // 가장 먼저 끝난 작업의 결과만 리턴
            String result = executor.invokeAny(tasks);
            System.out.println("가장 빠른 결과: " + result);
        } catch (InterruptedException | ExecutionException e) {
            System.out.println("예외 발생: " + e.getMessage());
        } finally {
            executor.shutdown();
        }
    }
}

 

 

 

 

 

8. 멀티 점원 햄버거 주문 처리

import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

class Order {
    private final int orderId;

    public Order(int orderId) {
        this.orderId = orderId;
    }

    public int getOrderId() {
        return orderId;
    }
}

class OrderProcessor implements Callable<String> {
    private final Order order;
    private final String workerName;
    private static final Random random = new Random();

    // 점원별 처리 속도 범위 (단위: 밀리초)
    private static final Map<String, int[]> workerSpeedMap = Map.of(
        "김사과", new int[]{1000, 2000},  // 빠름
        "반하나", new int[]{2000, 3000},  // 보통
        "오렌지", new int[]{3000, 4000}   // 느림
    );

    private static final AtomicInteger totalProcessed = new AtomicInteger(0);
    private static final Map<String, Integer> workerStats = new ConcurrentHashMap<>();

    public OrderProcessor(Order order, String workerName) {
        this.order = order;
        this.workerName = workerName;
    }

    @Override
    public String call() throws Exception {
        int[] speedRange = workerSpeedMap.get(workerName);
        int prepTime = random.nextInt(speedRange[1] - speedRange[0] + 1) + speedRange[0];
        Thread.sleep(prepTime);

        totalProcessed.incrementAndGet();
        workerStats.merge(workerName, 1, Integer::sum);

        return workerName + " - 주문 " + order.getOrderId() + "번 완료 (소요시간: " + prepTime + "ms)";
    }

    public static int getTotalProcessed() {
        return totalProcessed.get();
    }

    public static void printStats() {
        System.out.println("\n🧾 점원별 처리 주문 수:");
        workerStats.forEach((name, count) -> System.out.println("👨‍🍳 " + name + ": " + count + "건"));
    }
}

public class BurgerOrderSimulator {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        int totalOrders = 10;
        List<String> workers = List.of("김사과", "반하나", "오렌지");

        ExecutorService executor = Executors.newFixedThreadPool(workers.size());
        List<Future<String>> futures = new ArrayList<>();

        for (int i = 1; i <= totalOrders; i++) {
            String worker = workers.get(i % workers.size());
            Order order = new Order(i);
            futures.add(executor.submit(new OrderProcessor(order, worker)));
        }

        for (Future<String> future : futures) {
            System.out.println(future.get());
        }

        executor.shutdown();
        OrderProcessor.printStats();
        System.out.println("총 주문 처리 수: " + OrderProcessor.getTotalProcessed() + "건 🍔");
    }
}

'Server > Java' 카테고리의 다른 글

Socket  (0) 2025.06.02
JDBC  (0) 2025.05.30
DTO와 VO  (0) 2025.05.28
Generic  (0) 2025.05.27
java.lang package  (0) 2025.05.27

1. DTO

cf. Data : 메모리 속 데이터

DTO(Data Transfer Object)는 계층 간 또는 네트워크를 통한 데이터 전달을 위해 사용되는 객체로, 주로 컨트롤러와 서비스, 서비스와 레포지토리 간에 데이터를 주고받을 때 사용됩니다. DTO는 데이터 전달에 목적이 있으므로 비즈니스 로직(더하기, 빼기 etc..)을 포함하지 않으며, 필요한 데이터만 골라 담아 전송 효율성과 보안성을 높일 수 있습니다. 일반적으로 불변 객체는 아니며, getter와 setter 메서드를 포함하여 유연하게 데이터를 조작할 수 있도록 설계됩니다.

 

컨트롤러 (Controller)

컨트롤러는 사용자의 요청을 가장 먼저 받아들이는 계층으로, 웹 브라우저나 외부 클라이언트로부터 전달된 HTTP 요청(URL, GET/POST 등)을 받아 처리하는 역할을 합니다. 주로 사용자의 요청을 파라미터로 받아 적절한 서비스 계층으로 전달하고, 서비스의 처리 결과를 응답으로 반환하는 역할을 합니다.

 

서비스 (Service)

서비스는 컨트롤러와 레포지토리 사이에서 비즈니스 로직을 처리하는 핵심 계층입니다. 즉, "무엇을 할 것인가"에 대한 구체적인 로직이 이 계층에 위치하며, 여러 레포지토리를 조합하거나 조건에 따라 데이터를 가공하는 등의 로직이 들어갑니다. 컨트롤러는 요청을 받아 서비스로 전달하고, 서비스는 로직을 수행한 후 그 결과를 컨트롤러에 반환합니다.

 

레포지토리 (Repository)

레포지토리는 데이터베이스와 직접 연결되어 데이터를 저장, 조회, 수정, 삭제(CRUD)하는 역할을 합니다. 즉, "어디서 어떻게 데이터를 가져올 것인가"를 담당하며, JPA, MyBatis(sql을 따로 정리해주는 역할) 등의 ORM 프레임워크와 연결되어 실제 데이터 접근을 추상화합니다. 서비스 계층은 레포지토리를 통해 원하는 데이터를 얻고 비즈니스 로직을 처리합니다.

 

getter / setter

Getter와 Setter는 객체 지향 프로그래밍에서 클래스의 필드(속성)에 직접 접근하지 않고 간접적으로 접근할 수 있도록 도와주는 메서드입니다. Getter는 필드 값을 읽어오는 역할, Setter는 필드 값을 설정(변경)하는 역할을 하며, 일반적으로 private 필드를 외부에서 제어할 수 있도록 도와줍니다. 이를 통해 캡슐화(Encapsulation) 원칙을 지킬 수 있으며, 필요 시 내부 로직을 추가하여 값의 유효성 검사나 로깅 등도 구현할 수 있습니다.

 

영어 단어 사전 프로그램

package lesson07;

import java.time.LocalDate;
import java.util.*;

// 영어사전
class WordDTO{
    private String word;
    private String meaning;
    private String level;
    private LocalDate regDate;

    public WordDTO(String word, String meaning, String level) {
        this.word = word;
        this.meaning = meaning;
        this.level = level;
        // 날짜는 직접 넣도록 수정
        this.regDate = LocalDate.now();
    }


    // getter & setter 생성
    public String getWord() {
        return word;
    }

    public void setWord(String word) {
        this.word = word;
    }

    public String getMeaning() {
        return meaning;
    }

    public void setMeaning(String meaning) {
        this.meaning = meaning;
    }

    public String getLevel() {
        return level;
    }

    public void setLevel(String level) {
        this.level = level;
    }

    public LocalDate getRegDate() {
        return regDate;
    }

    public void setRegDate(LocalDate regDate) {
        this.regDate = regDate;
    }

    @Override
    public String toString() {
        return "[단어] "+word+"| 뜻:"+meaning+"| 레벨: "+level+"| 등록일: "+regDate;
    }
}

// controller 생성
class WordController{
    private final WordService service = new WordService();

    public void register(String word, String meaning, int levelNum) {
        String level =convertLevel((levelNum)) ;
        if(level==null){
            System.out.println("잘못된 레벨입니다.(1:초급, 2:중급, 3:고급");
            return;
        }try{
            WordDTO dto = new WordDTO (word,meaning,level);
            service.register(dto);
        }catch(IllegalArgumentException e){
            System.out.println("등록실패: "+e.getMessage());
        }
    }
    private String convertLevel(int levelNum) {
        return switch (levelNum){
            case 1 -> "초급";
            case 2 -> "중급";
            case 3 -> "고급";
            default -> null;
        };
    }
    // 내가 한 코드
//    public void list(){
//        service.list();
//    }
    // 강사님 코드
    public void printAllWords() {
        List<WordDTO> list = service.getAllWords();
        if(list.isEmpty()){
            System.out.println("등록된 단어가 없습니다.");
        }else{
            System.out.println("등록된 단어 목록");
            for(WordDTO dto : list){
                System.out.println("-"+dto);
            }
        }
    }
 public void query (String word) {
        WordDTO dto = service.findWord(word);
        if(dto==null){
            System.out.println(word+"단어는 등록되어 있지 않습니다.");
        }else{
            System.out.println("조회 결과: "+dto);
        }
 }
}

// Service 클래스 생성
class WordService{
    private final WordRepository repository = new WordRepository();
    public void register(WordDTO dto) {
        if(dto.getWord().isBlank() || dto.getMeaning().isBlank()){
            throw new IllegalArgumentException("단어와 뜻은 반드시 입력해야 합니다.");
        }
        repository.save(dto);
    }
    // 내가 한 코드
//    public void list(){
//        repository.list();
//    }
    // 강사님 코드
    public List<WordDTO> getAllWords() {
        return repository.findAll();
    }
    public WordDTO findWord(String word) {
        return repository.findByWord(word);
    }
}

// Repository 클래스 생성
class WordRepository{
     private final Map<String,WordDTO> wordMap = new HashMap<>();
    // Map 으로 쓴 이유, interface에 등록된 이름으로 써도 무방하기 때문에 (다형성)
    public void save(WordDTO dto) {
        wordMap.put(dto.getWord(),dto);
        System.out.println("저장 완료: "+dto.getWord());
    }
    // 내가 한 코드
//    public void list() {
//        for(Map.Entry<String,WordDTO> entry : wordMap.entrySet()){
//            System.out.println(entry.getKey()+": "+entry.getValue());
//        }
//    }
    // 강사님 코드
    public List<WordDTO> findAll() {
        return new ArrayList<>(wordMap.values());
    }
    public WordDTO findByWord(String word) {
        return wordMap.get(word);
    }
}


public class Ex06_Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        WordController controller = new WordController();

        // 무한루프
        while(true){
            System.out.println("영어 단어 사전");
            System.out.println("1. 단어 등록");
            System.out.println("2. 전체 단어 목록 보기");
            System.out.println("3. 단어 상세 조회");
            System.out.println("0. 종료");
            System.out.print("선택: ");
            int choice = Integer.parseInt(sc.nextLine());

            switch(choice) {
                case 0 -> {
                    System.out.println("프로그램을 종료합니다.");
                    sc.close();
                    return;
                }
                case 1 -> {
                    System.out.print("영단어 입력: ");
                    String word = sc.nextLine();
                    System.out.print("뜻 입력: ");
                    String meaning = sc.nextLine();
                    System.out.print("레벨 선택(1:초급, 2:중급, 3:고급): ");
                    int level = Integer.parseInt(sc.nextLine());


                    // 단어 등록
                    controller.register(word,meaning,level);
                }
                // 내가 한 코드
//                case 2 -> {
//                    System.out.println("등록한 목록 보기");
//                    controller.list();
//                }
                // 강사님 코드
                case 2 -> controller.printAllWords();
                case 3 -> {
                    System.out.print("조회할 영단어 입력: ");
                    String word = sc.nextLine();
                    controller.query(word);
                }
                default -> System.out.println("잘못된 메뉴입니다.");
            }
        }
    }
}

 

 

2. VO

VO(Value Object)는 값 자체로 객체의 동일성을 판단하는 객체로, 주로 도메인 모델에서 의미 있는 데이터를 표현할 때 사용됩니다. VO는 일반적으로 불변 객체로 설계되며, 내부 필드는 final로 선언하고, 생성자를 통해 값을 설정한 후 변경할 수 없습니다. 값이 같으면 같은 객체로 간주되기 때문에 equals()와 hashCode() 메서드를 반드시 오버라이드해야 하며, 주소, 좌표, 통화(Money) 등과 같이 실세계의 속성을 표현하는 데 자주 사용됩니다. VO는 객체의 식별자보다는 그 안에 담긴 데이터의 내용 자체가 중요할 때 적합합니다. 객체값과 동일한지 확인할 때 쓰입니다.

cf. 변화 X  (DTO 값이 변할 수 없음)

 

배달 가능 지역 확인 시스템

import java.util.Objects;

public class Point {
    private final int x;
    private final int y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    // 두 지점 간 거리 계산 (피타고라스)
    public double distanceTo(Point other) {
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    public int getX() { return x; }
    public int getY() { return y; }

    // VO의 핵심: 값이 같으면 동일한 객체로 간주
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Point)) return false;
        Point other = (Point) o;
        return x == other.x && y == other.y;
    }

    @Override
    public int hashCode() {
        return Objects.hash(x, y);
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}

 

public class StoreService {
    private final Point storeLocation = new Point(0, 0); // 가게 위치 기준점

    public boolean canDeliver(Point customerLocation) {
        double distance = storeLocation.distanceTo(customerLocation);
        System.out.println("📏 거리 계산: " + distance + "km");
        return distance <= 5.0;
    }
}

 

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        StoreService service = new StoreService();

        System.out.println("🏠 가게 기준점은 (0, 0)입니다.");
        System.out.print("고객 위치 X 좌표 입력: ");
        int x = Integer.parseInt(sc.nextLine());

        System.out.print("고객 위치 Y 좌표 입력: ");
        int y = Integer.parseInt(sc.nextLine());

        Point customer = new Point(x, y);
        System.out.println("🚚 고객 위치: " + customer);

        if (service.canDeliver(customer)) {
            System.out.println("✅ 배달 가능 지역입니다!");
        } else {
            System.out.println("❌ 배달 불가능한 지역입니다.");
        }

        sc.close();
    }
}

 

 

2. record

record는 자바 16부터 도입된 클래스 유형으로, 주로 값을 담는 간단한 데이터 객체(VO, DTO 등)를 간결하게 정의할 수 있도록 도와줍니다. record를 사용하면 생성자, getter, toString(), equals(), hashCode() 등의 메서드를 자동으로 생성해주며, 모든 필드는 자동으로 private final로 처리되어 불변(immutable) 객체가 됩니다. 복잡한 코드 없이 데이터를 저장하고 비교하는 용도로 자주 사용되며, 불필요한 보일러플레이트 코드를 줄이고 가독성과 유지보수성을 향상시킬 수 있습니다.

 

1. 영어 단어 사전 프로그램을 recode를 사용하여 변경

// WordDTO.java (record)
import java.time.LocalDate;

public record WordDTO(String word, String meaning, String level, LocalDate regDate) {
    public WordDTO(String word, String meaning, String level) {
        this(word, meaning, level, LocalDate.now());
    }

    @Override
    public String toString() {
        return "[단어] " + word + " | 뜻: " + meaning + " | 레벨: " + level + " | 등록일: " + regDate;
    }
}

// WordRepository.java
import java.util.*;

public class WordRepository {
    private final Map<String, WordDTO> wordMap = new HashMap<>();

    public void save(WordDTO dto) {
        wordMap.put(dto.word(), dto);
        System.out.println("✅ 저장 완료: " + dto.word());
    }

    public WordDTO findByWord(String word) {
        return wordMap.get(word);
    }

    public List<WordDTO> findAll() {
        return new ArrayList<>(wordMap.values());
    }
}

// WordService.java
import java.util.List;

public class WordService {
    private final WordRepository repository = new WordRepository();

    public void registerWord(WordDTO dto) {
        if (dto.word().isBlank() || dto.meaning().isBlank()) {
            throw new IllegalArgumentException("단어와 뜻은 반드시 입력해야 합니다.");
        }
        repository.save(dto);
    }

    public WordDTO findWord(String word) {
        return repository.findByWord(word);
    }

    public List<WordDTO> getAllWords() {
        return repository.findAll();
    }
}

// WordController.java
import java.util.List;

public class WordController {
    private final WordService service = new WordService();

    public void register(String word, String meaning, int levelNum) {
        String level = convertLevel(levelNum);
        if (level == null) {
            System.out.println("❌ 잘못된 레벨입니다. (1: 초급, 2: 중급, 3: 고급)");
            return;
        }

        try {
            WordDTO dto = new WordDTO(word, meaning, level);
            service.registerWord(dto);
        } catch (IllegalArgumentException e) {
            System.out.println("❌ 등록 실패: " + e.getMessage());
        }
    }

    public void printAllWords() {
        List<WordDTO> list = service.getAllWords();
        if (list.isEmpty()) {
            System.out.println("📂 등록된 단어가 없습니다.");
        } else {
            System.out.println("📘 등록된 단어 목록:");
            for (WordDTO dto : list) {
                System.out.println("- " + dto);
            }
        }
    }

    public void query(String word) {
        WordDTO dto = service.findWord(word);
        if (dto == null) {
            System.out.println("🔍 '" + word + "' 단어는 등록되어 있지 않습니다.");
        } else {
            System.out.println("🔎 조회 결과: " + dto);
        }
    }

    private String convertLevel(int levelNum) {
        return switch (levelNum) {
            case 1 -> "초급";
            case 2 -> "중급";
            case 3 -> "고급";
            default -> null;
        };
    }
}

// Main.java
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        WordController controller = new WordController();

        while (true) {
            System.out.println("\n=== 영어 단어 사전 ===");
            System.out.println("1. 단어 등록");
            System.out.println("2. 전체 단어 목록 보기");
            System.out.println("3. 단어 상세 조회");
            System.out.println("0. 종료");
            System.out.print("선택: ");
            int choice = Integer.parseInt(sc.nextLine());

            switch (choice) {
                case 0 -> {
                    System.out.println("👋 프로그램을 종료합니다.");
                    sc.close();
                    return;
                }
                case 1 -> {
                    System.out.print("영단어 입력: ");
                    String word = sc.nextLine();

                    System.out.print("뜻 입력: ");
                    String meaning = sc.nextLine();

                    System.out.print("레벨 선택 (1: 초급, 2: 중급, 3: 고급): ");
                    int level = Integer.parseInt(sc.nextLine());

                    controller.register(word, meaning, level);
                }
                case 2 -> controller.printAllWords();
                case 3 -> {
                    System.out.print("조회할 영단어 입력: ");
                    String word = sc.nextLine();
                    controller.query(word);
                }
                default -> System.out.println("❗ 잘못된 메뉴입니다.");
            }
        }
    }
}

 

2. 배달 가능 지역 확인 시스템

// Point.java (record for VO)
public record Point(int x, int y) {
    public double distanceTo(Point other) {
        int dx = this.x - other.x;
        int dy = this.y - other.y;
        return Math.sqrt(dx * dx + dy * dy);
    }

    @Override
    public String toString() {
        return "(" + x + ", " + y + ")";
    }
}

// StoreService.java
public class StoreService {
    private final Point storeLocation = new Point(0, 0); // 가게 위치 고정

    public boolean canDeliver(Point customerLocation) {
        double distance = storeLocation.distanceTo(customerLocation);
        System.out.println("📏 거리 계산: " + distance + "km");
        return distance <= 5.0;
    }
}

// DeliveryMain.java
import java.util.Scanner;

public class DeliveryMain {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        StoreService storeService = new StoreService();

        System.out.println("🏪 가게 위치는 (0, 0)입니다.");

        System.out.print("고객 위치 X 입력: ");
        int x = Integer.parseInt(sc.nextLine());

        System.out.print("고객 위치 Y 입력: ");
        int y = Integer.parseInt(sc.nextLine());

        Point customer = new Point(x, y);
        System.out.println("🚚 고객 위치: " + customer);

        if (storeService.canDeliver(customer)) {
            System.out.println("✅ 배달 가능 지역입니다!");
        } else {
            System.out.println("❌ 배달 불가능한 지역입니다.");
        }

        sc.close();
    }
}

'Server > Java' 카테고리의 다른 글

JDBC  (0) 2025.05.30
Thread  (0) 2025.05.29
Generic  (0) 2025.05.27
java.lang package  (0) 2025.05.27
Exception Handling  (0) 2025.05.27

1. 제네릭

** 여러가지 타입을 중복없이 하나의 타입으로 통일성있게 사용할 수 있게해주는 기능

 

제네릭(Generic)은 자바에서 클래스나 메서드를 선언할 때 사용할 데이터 타입을 나중에 지정할 수 있도록 하는 기능입니다. 이를 통해 코드의 재사용성과 타입 안정성을 높일 수 있으며, 컴파일 시 타입 검사를 가능하게 해줍니다. 예를 들어 List<String>(제네릭 타입)처럼 사용할 경우, 리스트에 문자열만 담을 수 있도록 제한되어 런타임 오류를 줄일 수 있습니다. 제네릭은 다양한 타입을 처리해야 하는 클래스나 메서드에서 중복 없이 하나의 코드로 여러 타입을 다룰 수 있도록 도와줍니다.

 

1. 제네릭<> 을 사용하는 이유

  • 타입 안정성
    → 컴파일 시점에 타입을 검사하여 오류를 줄일 수 있습니다.
  • 형변환 제거
    → 제네릭을 사용하면 Object로 저장 후 꺼낼 때 형변환을 하지 않아도 됩니다.
  • 코드 재사용성 증가
    → 다양한 타입에 대해 같은 로직을 사용할 수 있습니다.
class 클래스이름<T> {
    // T는 타입 파라미터 (예: String, Integer 등)
    private T 변수;

    public void set(T 변수) {
        this.변수 = 변수;
    }

    public T get() {
        return 변수;
    }
}

 

T는 타입 변수(Type Parameter)이며, 어떤 타입이 들어올지 모를 때 사용합니다. 보통 T, E, K, V 등 대문자로 표현합니다.

  •  T : type
  •  E :  element
  •  K : key value -- < K , V >
  •  V : etc..

 

제네릭을 사용하지 않은 경우

Object를 사용하면 다양한 타입을 넣을 수 있지만, 꺼낼 때 형변환(casting)이 필요하며, 실행 중(ClassCastException) 오류가 날 수 있습니다.

class ObjectBox {
    private Object item;

    public void set(Object item) {
        this.item = item;
    }

    public Object get() {
        return item;
    }
}

public class NoGenericExample {
    public static void main(String[] args) {
        ObjectBox box = new ObjectBox();

        box.set("사과");  // 문자열 저장
        Object obj = box.get();

        // 강제 형변환 필요
        String fruit = (String) obj;
        System.out.println("과일: " + fruit);

        box.set(123);  // 정수도 저장 가능 (타입 제한 없음)
        // 잘못된 형변환 시 에러 발생
        // String error = (String) box.get();  // 런타임 오류 발생
    }
}

 

제네릭을 사용한 경우

제네릭을 사용하면 저장할 때 타입을 제한할 수 있고, 꺼낼 때 형변환이 필요 없습니다.

// 제네릭 Box 클래스
class Box<T> {
    private T item;

    public void set(T item) {
        this.item = item;
    }

    public T get() {
        return item;
    }
}

public class GenericExample {
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>();
        stringBox.set("바나나");  // String 타입만 저장 가능
        String fruit = stringBox.get();  // 형변환 없이 꺼냄
        System.out.println("과일: " + fruit);

        Box<Integer> intBox = new Box<>();
        intBox.set(100);
        int number = intBox.get();  // 형변환 없이 꺼냄
        System.out.println("숫자: " + number);
    }
}

 

2. 제네릭 클래스

class Person<T, U> {
    private T name;
    private U age;

    public Person(T name, U age) {
        this.name = name;
        this.age = age;
    }

    public void printInfo() {
        System.out.println("이름: " + name + ", 나이: " + age);
    }
}

public class Main {
    public static void main(String[] args) {
        Person<String, Integer> p1 = new Person<>("김사과", 20);
        p1.printInfo();

        Person<String, Double> p2 = new Person<>("반하나", 23.5);
        p2.printInfo();
    }
}

 

3. 제네릭 메서드

class Printer {
    public <T> void print(T item) {
        System.out.println("출력: " + item);
    }
}

public class Main {
    public static void main(String[] args) {
        Printer printer = new Printer();

        printer.print("안녕");
        printer.print(123);
        printer.print(3.14);
        printer.print(true);
    }
}

 

 

2. 컬렉션 프레임워크

cf. wrapper --> Generic --> Collection Framework

 

더보기

컬렉션 프레임워크(Collection Framework)는 자바에서 데이터를 효율적으로 저장하고 처리할 수 있도록 다양한 자료구조(리스트(= 배열), 집합, 맵 등)와 알고리즘을 제공하는 표준화된 클래스 집합입니다. 이를 통해 배열보다 더 유연하고 강력한 방식으로 데이터를 추가, 삭제, 검색할 수 있으며, 제네릭을 활용해 타입 안정성도 확보할 수 있습니다. 대표적으로 List, Set, Map 인터페이스와 이를 구현한 다형성을 가지고 ArrayList, HashSet, HashMap 등의 클래스가 포함되어 있으며, 컬렉션 간 변환이나 정렬, 반복 처리 등의 기능도 제공합니다.

 

** 메모리에 데이터를 저장해 어떻게 꺼낼지에 대한 알고리즘을 사용

그럼 DB를 왜 사용함? => 영구적인 저장을 위해서

 

** LinkedList : point칸을 만들어 다음 주소를 기억하게 만들어, 데이터를 넣더라도 빠른 속도로 해당 데이터를 찾을 수 있다.

** Tree Map :  검색일 때, 속도가 빠름

 

https://inpa.tistory.com/entry/JCF-%F0%9F%A7%B1-Collections-Framework-%EC%A2%85%EB%A5%98-%EC%B4%9D%EC%A0%95%EB%A6%AC

 

🧱 Java Collections Framework 종류 💯 총정리

Java Collection Framework 자바 새내기분들은 컬렉션 프레임워크라는 단어에 뭔가 거창하고 어려운 느낌이 들수 있겠지만, 그냥 자료 구조(Data Structure) 종류의 형태들을 자바 클래스로 구현한 모음집

inpa.tistory.com

https://visualgo.net/en 코린이 참고

 

visualising data structures and algorithms through animation - VisuAlgo

VisuAlgo is generously offered at no cost to the global Computer Science community. If you appreciate VisuAlgo, we kindly request that you spread the word about its existence to fellow Computer Science students and instructors. You can share VisuAlgo throu

visualgo.net

 

1. List 계열 ( List Interpace을 상속받음)

  • 순서(인덱스)를 유지하며 저장
  • 배열처럼 사이즈를 저장하지 않아도됨.
  • generic 사용가능
  • 중복된 요소 허용
  • 요소를 인덱스로 접근 가능 (배열처럼 list.get(0) 형태)
ArrayList 내부적으로 배열 사용, 빠른 검색, 느린 삽입/삭제 (특히 중간 삽입)
LinkedList 이중 연결 리스트 구조, 빠른 삽입/삭제, 느린 검색
Vector ArrayList와 유사하지만 동기화 처리 되어 멀티스레드에 적합
Stack Vector를 상속한 후 LIFO 구조를 따르는 클래스 (push/pop 지원)

 

import java.util.ArrayList;

public class StudentListExample {
    public static void main(String[] args) {
        // 1. ArrayList 생성
        ArrayList<String> students = new ArrayList<>();

        // 2. 요소 추가
        students.add("김사과");
        students.add("반하나");
        students.add("오렌지");

        // 3. 요소 삽입 (중간에 추가)
        students.add(1, "이메론");  // 1번 인덱스에 삽입 → 자동으로 뒤로 밀림

        // 4. 요소 출력
        System.out.println("전체 학생 목록:");
        for (String name : students) {
            System.out.println("- " + name);
        }

        // 5. 요소 검색
        String student = students.get(2);  // 인덱스로 접근
        System.out.println("\n2번 인덱스의 학생: " + student);

        // 6. 요소 수정
        students.set(2, "배애리");
        System.out.println("\n수정 후 2번 인덱스: " + students.get(2));

        // 7. 요소 삭제
        students.remove("이메론");         // 이름으로 삭제
        students.remove(0);               // 인덱스로 삭제

        // 8. 크기 확인
        System.out.println("\n현재 학생 수: " + students.size());

        // 9. 최종 목록 출력
        System.out.println("\n최종 학생 목록:");
        for (int i = 0; i < students.size(); i++) {
            System.out.println(i + ": " + students.get(i));
        }
    }
}

 

import java.util.LinkedList;

public class HospitalQueue {
    public static void main(String[] args) {
        // 1. LinkedList 생성 (문자열 타입)
        LinkedList<String> queue = new LinkedList<>();

        // 2. 환자 도착 (뒤에 추가)
        queue.add("김사과");
        queue.add("반하나");
        queue.add("오렌지");

        // 3. 긴급 환자 도착 (앞에 추가)
        queue.addFirst("이메론");  // 가장 앞에 삽입

        // 4. 대기열 출력
        System.out.println("현재 대기열:");
        for (String name : queue) {
            System.out.println("- " + name);
        }

        // 5. 진료 (앞에서 한 명씩 제거)
        System.out.println("\n진료 시작:");
        while (!queue.isEmpty()) {
            String patient = queue.removeFirst();  // 맨 앞 제거
            System.out.println("진료 중: " + patient);
        }

        // 6. 대기열 확인
        System.out.println("\n진료 완료. 남은 환자 수: " + queue.size());
    }
}

 

import java.util.Vector;

public class StudentScoreManager {
    public static void main(String[] args) {
        // 1. Vector 생성 (Integer 타입으로 학생 점수 저장)
        Vector<Integer> scores = new Vector<>();

        // 2. 점수 추가
        scores.add(90);  // 김사과
        scores.add(85);  // 반하나
        scores.add(78);  // 오렌지
        scores.add(92);  // 이메론
        scores.add(88);  // 배애리

        // 3. 전체 점수 출력
        System.out.println("전체 학생 점수:");
        for (int i = 0; i < scores.size(); i++) {
            System.out.println("- " + getName(i) + ": " + scores.get(i) + "점");
        }

        // 4. 평균 계산
        int sum = 0;
        for (int score : scores) {
            sum += score;
        }
        double average = (double) sum / scores.size();
        System.out.println("\n전체 평균 점수: " + average);

        // 5. 점수 수정 (오렌지의 점수 수정)
        scores.set(2, 80);  // 인덱스 2번 오렌지
        System.out.println("\n오렌지 점수 수정 후: " + scores.get(2) + "점");

        // 6. 마지막 학생 삭제 (배애리)
        scores.remove(scores.size() - 1);
        System.out.println("\n마지막 학생(배애리) 삭제 후 학생 수: " + scores.size());
    }

    // 인덱스에 따른 이름 반환
    public static String getName(int index) {
        switch (index) {
            case 0: return "김사과";
            case 1: return "반하나";
            case 2: return "오렌지";
            case 3: return "이메론";
            case 4: return "배애리";
            default: return "이름없음";
        }
    }
}

 

2. Set 계열

  • 중복을 허용하지 않음
  • 순서가 없음 (일반적으로 입력 순서를 보장하지 않음)
  • 하나의 집합(수학의 Set 개념과 유사)

cf. Hash : 임의의 길이의 데이터를 고정된 길이의 데이터로 매핑하는 함수

HashSet 순서를 유지하지 않음, 가장 일반적인 Set, 단방향 암호구성
LinkedHashSet 입력 순서를 유지
TreeSet 자동 정렬됨 (오름차순)--> 속도가 느림 , 내부적으로 트리 구조 사용, 검색이 빠름 
import java.util.HashSet;

public class AttendanceChecker {
    public static void main(String[] args) {
        // 1. HashSet 생성
        HashSet<String> attendance = new HashSet<>();

        // 2. 출석 체크
        attendance.add("김사과");
        attendance.add("반하나");
        attendance.add("오렌지");
        attendance.add("이메론");
        attendance.add("배애리");

        // 중복 출석 시도
        attendance.add("김사과");
        attendance.add("반하나");

        // 3. 전체 명단 출력
        System.out.println("📋 출석한 학생 명단:");
        for (String name : attendance) {
            System.out.println("- " + name);
        }

        // 4. 특정 학생 출석 여부 확인
        System.out.println("\n🔍 '오렌지' 출석 여부: " + attendance.contains("오렌지"));
        System.out.println("🔍 '박수박' 출석 여부: " + attendance.contains("박수박"));

        // 5. 학생 삭제
        attendance.remove("이메론");
        System.out.println("\n🗑️ '이메론' 삭제 후 출석 명단:");
        for (String name : attendance) {
            System.out.println("- " + name);
        }
    }
}

 

import java.util.TreeSet;

public class SortedStudentSet {
    public static void main(String[] args) {
        // 1. TreeSet 생성 (문자열 타입)
        TreeSet<String> students = new TreeSet<>();

        // 2. 학생 이름 추가
        students.add("김사과");
        students.add("반하나");
        students.add("오렌지");
        students.add("이메론");
        students.add("배애리");

        // 3. 중복 추가 시도
        students.add("김사과");  // 무시됨

        // 4. 자동 정렬된 학생 목록 출력
        System.out.println("📚 이름순으로 정렬된 학생 명단:");
        for (String name : students) {
            System.out.println("- " + name);
        }

        // 5. 특정 이름보다 작은 값, 큰 값 찾기
        System.out.println("\n🧐 '오렌지'보다 앞에 오는 이름: " + students.lower("오렌지"));
        System.out.println("🧐 '오렌지'보다 뒤에 오는 이름: " + students.higher("오렌지"));
    }
}

 

TreeSet 특징

정렬 저장 자동으로 오름차순 정렬 (String, Integer 등 Comparable (-->오버라이딩하면 내림차순으로 변경 가능) 필요) 
중복 허용 ❌ 허용하지 않음
탐색 기능 lower(), higher(), floor(), ceiling() 등
성능 이진 탐색 트리 기반 → 삽입, 삭제, 검색 모두 O(log n)
정렬 기준 변경 Comparator를 생성자에 전달하여 커스텀 정렬 가능

cf. 빅오(Big-O) 표기법 :

n에 1과같은 상수가 들어갈 때 가장빠름
TreeSet 구조

class Student {
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return name + " (" + score + "점)";
    }
}

 

import java.util.TreeSet;

class Student implements Comparable<Student> {
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    // 기본 정렬 기준: 점수 오름차순
    @Override
    public int compareTo(Student other) {
        return Integer.compare(this.score, other.score);
    }

    @Override
    public String toString() {
        return name + " (" + score + "점)";
    }
}

public class TreeSetComparableExample {
    public static void main(String[] args) {
        TreeSet<Student> set = new TreeSet<>();
        set.add(new Student("김사과", 90));
        set.add(new Student("반하나", 85));
        set.add(new Student("오렌지", 95));
        set.add(new Student("이메론", 92));
        set.add(new Student("배애리", 85));  // 같은 점수 → 중복 간주 안되지만 순서 주의

        System.out.println("🏅 점수 오름차순 정렬:");
        for (Student s : set) {
            System.out.println("- " + s);
        }
    }
}

 

import java.util.TreeSet;
import java.util.Comparator;

class Student {
    String name;
    int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public String toString() {
        return name + " (" + score + "점)";
    }
}

public class TreeSetComparatorExample {
    public static void main(String[] args) {
        // 이름 기준 내림차순 정렬 Comparator 정의
        Comparator<Student> nameDesc = (a, b) -> b.name.compareTo(a.name);

        TreeSet<Student> set = new TreeSet<>(nameDesc);
        set.add(new Student("김사과", 90));
        set.add(new Student("반하나", 85));
        set.add(new Student("오렌지", 95));
        set.add(new Student("이메론", 92));
        set.add(new Student("배애리", 85));

        System.out.println("🔤 이름 내림차순 정렬:");
        for (Student s : set) {
            System.out.println("- " + s);
        }
    }
}

 

3. Map 계열 ⭐⭐

  • 데이터를 Key-Value 쌍으로 저장(유사 JSON)
  • Key는 중복될 수 없음, Value는 중복 가능
  • 값을 검색할 때는 key를 사용 (map.get("name"))
HashMap 가장 많이 사용됨. 키 순서 없음
LinkedHashMap 입력 순서 유지
TreeMap 키 기준 정렬 (Comparable 또는 Comparator 필요)
Hashtable HashMap과 유사하나, 동기화 지원, null 키 불가

 

package lesson07;

import java.util.HashMap;
import java.util.Map;

public class Ex05_Main {
    public static void main(String[] args) {
        HashMap<String, Integer> scoreMap = new HashMap<>();
        // scoreMap.put(Key, Value);
        // Key는 중복 X , Value 중복 O
        scoreMap.put("김사과", 90);
        scoreMap.put("반하나", 85);
        scoreMap.put("오렌지", 95);
        scoreMap.put("이메론", 88);
        scoreMap.put("배애리", 92);


        // System.out.println(scoreMap.entrySet());
        // {반하나=85, 이메론=88, 김사과=90, 오렌지=95, 배애리=92}

        // alt + enter --> import 실행
        // entrySet : 전체 key,value를 가져오는 함수
        for(Map.Entry<String,Integer> entry:scoreMap.entrySet()){
            // 키 = 값
            System.out.println(entry.getKey() + ": " + entry.getValue()+"점");
        }

        String name = "오렌지";
        System.out.println(name + "의 점수 : "+scoreMap.get(name)+"점");

        // 동일한 key를 넣으면 현재 넣은 value 값으로 업데이트 됨.
        scoreMap.put("김사과",100);
        System.out.println("김사과의 점수 수정 후: "+scoreMap.get("김사과")+"점" );

        // 삭제
        scoreMap.remove("반하나");
        for(Map.Entry<String,Integer> entry:scoreMap.entrySet()){
            System.out.println(entry.getKey() + ": " + entry.getValue()+"점");
        }

        System.out.println("전체 학생 수: "+scoreMap.size()+"명");
    }
}

'Server > Java' 카테고리의 다른 글

Thread  (0) 2025.05.29
DTO와 VO  (0) 2025.05.28
java.lang package  (0) 2025.05.27
Exception Handling  (0) 2025.05.27
Interface  (0) 2025.05.27

1. java.lang 패키지

java.lang 패키지는 자바에서 가장 기본적이고 필수적인 클래스들을 포함하는 패키지로, 자바 프로그램에서 자동으로 import되기 때문에 별도로 import 문 없이도 사용할 수 있습니다. 이 패키지에는 문자열을 처리하는 String, 수학 계산을 위한 Math, 객체의 최상위 클래스인 Object, 쓰레드 관련 클래스인 Thread, 예외 처리에 사용되는 Throwable 계열 클래스 등 자바 프로그래밍의 핵심적인 기능을 담당하는 클래스들이 포함되어 있습니다. 자바에서 거의 모든 프로그램이 이 패키지의 클래스를 사용하므로, java.lang은 자바의 핵심 인프라라고 할 수 있습니다.

 

1. 주요 클래스

Object 자바의 모든 클래스의 최상위 부모 클래스
String 문자열을 다루는 불변 클래스
StringBuilder, StringBuffer 가변 문자열 처리 클래스
Math 수학 관련 함수 (제곱근, 삼각함수, 난수 등)
Integer, Double 등 기본형을 객체로 다루는 래퍼 클래스
System 표준 입출력, 종료 등 시스템 관련 기능
Thread(= 작업단위), Runnable 멀티스레드 프로그래밍을 위한 클래스
Throwable, Exception, Error 예외 및 오류 처리 클래스

 

2. StringBuilder와 StringBuffer

  • 가변(mutable) 문자열을 다룰 수 있는 클래스
  • 문자열을 반복해서 조작할 때 메모리 낭비를 줄이고 성능 향상에 도움
  • 주로 문자열 덧붙이기, 삽입, 삭제, 반전 등을 효율적으로 처리

주요 메서드

.append(String) 문자열 추가
.insert(int, String) 특정 위치에 문자열 삽입
.delete(int, int) 문자열 일부 삭제
.reverse() 문자열 뒤집기
.toString() 최종 문자열로 변환

 

public class StringBuilderExample {
    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder();

        sb.append("Hello");
        sb.append(" ");
        sb.append("World");
        sb.insert(6, "Java ");         // 6번째 위치에 "Java " 삽입
        sb.delete(0, 6);               // "Hello " 삭제
        sb.reverse();                  // 문자열 뒤집기

        System.out.println("최종 문자열: " + sb.toString());  // 출력: !dlroW avaJ
    }
}

 

public class StringBufferExample {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("Hello");

        sb.append(" Thread");
        sb.insert(5, " Safe");
        System.out.println(sb.toString());  // Hello Safe Thread
    }
}

 

public class CompareString {
    public static void main(String[] args) {
        long start, end;

        // String (느림)
        start = System.currentTimeMillis();
        String str = "";
        for (int i = 0; i < 10000; i++) {
            str += "a";
        }
        end = System.currentTimeMillis();
        System.out.println("String 시간: " + (end - start) + "ms");

        // StringBuilder (빠름)
        start = System.currentTimeMillis();
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 10000; i++) {
            sb.append("a");
        }
        end = System.currentTimeMillis();
        System.out.println("StringBuilder 시간: " + (end - start) + "ms");
    }
}

 

3. Math 클래스

java.lang.Math 클래스는 자바에서 수학 계산을 도와주는 유틸리티 클래스입니다. 이 클래스는 정적(static) 메서드로만 구성되어 있어서, 객체를 생성하지 않고도 바로 사용할 수 있습니다.

주요 기능

절댓값 abs(x) 절댓값 반환
최댓값/최솟값 max(a, b), min(a, b) 둘 중 큰 값/작은 값 반환
제곱/제곱근 pow(a, b), sqrt(x) 거듭제곱 / 제곱근
반올림 round(x) 소수 첫째 자리에서 반올림
올림/버림 ceil(x), floor(x) 올림, 버림 반환
삼각 함수 sin(x), cos(x), tan(x) 라디안 기준의 삼각함수
로그 log(x) 자연로그 (밑 e)
지수 exp(x) e^x 반환
난수 random() 0.0 이상 1.0 미만의 랜덤 실수 반환

 

public class MathExample1 {
    public static void main(String[] args) {
        System.out.println("절댓값: " + Math.abs(-10));       // 10
        System.out.println("최댓값: " + Math.max(7, 3));      // 7
        System.out.println("최솟값: " + Math.min(7, 3));      // 3
    }
}

 

public class MathExample2 {
    public static void main(String[] args) {
        System.out.println("2의 3제곱: " + Math.pow(2, 3));     // 8.0
        System.out.println("16의 제곱근: " + Math.sqrt(16));    // 4.0
    }
}

 

public class MathExample3 {
    public static void main(String[] args) {
        double num = 3.65;
        System.out.println("반올림: " + Math.round(num));  // 4
        System.out.println("올림: " + Math.ceil(num));     // 4.0
        System.out.println("버림: " + Math.floor(num));    // 3.0
    }
}

 

public class MathExample4 {
    public static void main(String[] args) {
        // 0.0 이상 1.0 미만의 난수
        double rand = Math.random();
        System.out.println("난수: " + rand);

        // 1부터 10 사이의 정수 난수
        int randomInt = (int)(Math.random() * 10) + 1;
        System.out.println("1~10 정수 난수: " + randomInt);
    }
}

 

 

2. Wrapper 클래스

자바의 Wrapper 클래스(래퍼 클래스)는 int, double, char 같은 기본 자료형(primitive type)을 객체처럼 다룰 수 있도록 감싸주는 클래스입니다. 예를 들어, int는 Integer, double은 Double, char는 Character라는 클래스가 각각 존재하며, 이들을 통해 기본형을 객체로 변환하거나, 문자열을 숫자로 바꾸는 등의 유용한 기능을 사용할 수 있습니다. Wrapper 클래스는 컬렉션 클래스(List, Set 등)에 기본형 데이터를 저장할 때, 형변환 및 null 처리, 기본형과 객체 간 자동 변환(Auto Boxing/Unboxing) 등에 자주 사용됩니다.

1. 기본형과 대응되는 래퍼 클래스(= 숫자나 문자을 기본타입처럼 감싸주는..)

 

2. 박싱과 언박싱

박싱(Boxing)

기본 자료형(primitive type)을 해당 래퍼 클래스 객체로 변환하는 과정을 말합니다. 예를 들어 int → Integer, double → Double과 같이 기본형을 객체로 감싸는 것입니다.

int num = 100;
Integer boxed = Integer.valueOf(num);  // 박싱

 

public class BoxingExample {
    public static void main(String[] args) {
        int num = 100;
        Integer boxed = Integer.valueOf(num);  // 수동 박싱
        System.out.println("박싱된 객체: " + boxed);
    }
}

 

언박싱(Unboxing)

래퍼 클래스 객체에서 기본 자료형 값을 꺼내는 과정을 말합니다. 즉, Integer → int, Double → double과 같이 객체에서 원래 값(기본형)을 추출합니다.

Integer boxed = new Integer(200);
int num = boxed.intValue();  // 언박싱

 

public class UnboxingExample {
    public static void main(String[] args) {
        Integer boxed = new Integer(200);
        int num = boxed.intValue();  // 수동 언박싱
        System.out.println("기본형으로 변환: " + num);
    }
}

 

3. 오토박싱과 오토언박싱

오토박싱(Auto-boxing)

기본형을 자동으로 래퍼 클래스로 변환해주는 기능입니다. 개발자가 valueOf()를 직접 호출하지 않아도 컴파일러가 자동으로 박싱을 처리해줍니다.

Integer a = 10;  // 컴파일러가 Integer.valueOf(10) 으로 자동 변환

 

오토언박싱(Auto-unboxing)

래퍼 클래스 객체를 자동으로 기본형으로 변환해주는 기능입니다. 개발자가 intValue(), doubleValue()를 호출하지 않아도 자동으로 값을 꺼내줍니다.

Integer a = 10;  // 오토박싱
int b = a + 5;   // 오토언박싱: a.intValue() + 5

 

래퍼 객체가 null일 때 오토언박싱을 하면 예외가 발생하므로 주의해야 합니다.

Integer a = null;
int b = a;  // NullPointerException 발생!

 

public class AutoBoxingExample {
    public static void main(String[] args) {
        Integer a = 10;  // 자동 박싱 (int → Integer)
        int b = a + 5;   // 자동 언박싱 (Integer → int)
        System.out.println("결과: " + b);
    }
}

'Server > Java' 카테고리의 다른 글

DTO와 VO  (0) 2025.05.28
Generic  (0) 2025.05.27
Exception Handling  (0) 2025.05.27
Interface  (0) 2025.05.27
abstract  (0) 2025.05.21

+ Recent posts