1. 기초 문법과 흐름 제어
| Java 언어 개요 |
JDK, JRE, JVM, 바이트코드 이해 |
| 변수와 자료형 |
기본형 vs 참조형, Wrapper 클래스 |
| 연산자 |
산술, 논리, 비교, 비트, instanceof 등 |
| 조건문 & 반복문 |
if, switch, for, while, do-while |
| 배열과 문자열 |
배열 선언과 조작, String, StringBuilder, StringBuffer |
| 메서드 |
매개변수, 반환값, 오버로딩 |
1. Java 언어 개요
Java는 Sun Microsystems(현재는 Oracle)에 의해 1995년에 개발된 객체 지향 프로그래밍 언어이다.
"Write Once, Run Anywhere"라는 철학을 바탕으로, 한 번 작성한 코드를 다양한 운영체제에서 실행할 수 있도록 설계되었다.
주요 특징
- 플랫폼 독립성 (JVM 덕분에 다양한 OS에서 동일한 코드 실행 가능)
- 객체 지향 프로그래밍(OOP) 지원 (상속, 캡슐화, 다형성 등)
- 자동 메모리 관리 (Garbage Collection)
- 멀티스레딩, 풍부한 표준 API 제공
2. JDK (Java Development Kit: Java SDK(Software Development Kit))
JDK는 Java 애플리케이션을 개발하기 위한 도구 모음이다. Java 프로그램을 작성하고, 컴파일하고, 디버깅하고, 실행할 수 있도록 지원한다.
구성 요소
- javac: Java 소스 파일을 컴파일하여 바이트코드(.class)로 변환하는 컴파일러
- java: JVM을 통해 바이트코드를 실행시키는 명령어
- javadoc: 문서 자동 생성기
- jdb: 디버깅 도구
- API 라이브러리: 자주 사용하는 클래스/인터페이스들이 포함된 표준 라이브러리
- JRE: Java Runtime Environment 포함
3. JRE (Java Runtime Environment)
JRE는 Java 프로그램을 실행하기 위한 환경이다. 개발 도구는 포함되어 있지 않으며, 실행에 필요한 요소만 포함한다. Java 애플리케이션을 사용하기만 하는 사용자에게 필요하다.
구성 요소
- JVM (Java Virtual Machine)
- Java 클래스 라이브러리 (예: java.lang, java.util 등)
- 지원 파일 (프로퍼티, 설정 등)
4. JVM (Java Virtual Machine)
JVM은 Java 프로그램을 실행하기 위한 가상 머신이다. 바이트코드(.class 파일)를 해석하거나 기계어로 변환하여 실행한다. JVM은 플랫폼에 따라 구현이 다르지만, 바이트코드를 동일하게 해석하므로 Java는 플랫폼 독립성을 가진다.
역할
- 클래스 로딩: 클래스 로더를 통해 바이트코드를 JVM으로 로딩
- 실행 엔진: 바이트코드를 해석하거나 JIT 컴파일을 통해 실행
- 메모리 관리: 힙, 스택, 메서드 영역 등의 구조를 관리하며 가비지 컬렉션 수행
- 보안 관리: 실행 중인 코드에 대한 접근 제어 및 검사 수행
5. 바이트코드 (Bytecode)
바이트코드는 .java 소스 파일을 컴파일한 결과물이며 .class 확장자를 갖는다. JVM은 이 바이트코드를 입력으로 받아 실행한다. 바이트코드는 인간이 이해하기 어려운 중간 코드이며, 플랫폼 독립성을 제공하는 핵심이다.
6. 실행 흐름 요약
- Java 소스 작성 (Hello.java)
- javac Hello.java → 바이트코드 파일 Hello.class 생성
- java Hello → JVM이 바이트코드를 해석하고 실행
예제
// Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}
javac Hello.java // 컴파일 → Hello.class 생성
java Hello // JVM 실행 → "Hello, Java!" 출력
포함 관계 정리
| JDK |
개발 도구 (javac, java 등 포함) |
JDK ⊃ JRE ⊃ JVM |
| JRE |
실행 환경 (라이브러리 + JVM) |
JRE ⊃ JVM |
| JVM |
바이트코드 실행기 |
JVM |
| 바이트코드 |
JVM이 실행하는 중간 코드(.class) |
JDK의 컴파일러가 생성함 |
Java Applet
- 웹 브라우저(주로 인터넷 익스플로러 환경)에서 실행
- 특징: main() 메서드가 없이 Applet을 상속 받아 실행한다.
// HelloApplet.java
import java.applet.*;
import java.awt.*;
public class HelloApplet extends Applet {
public void paint(Graphics g) {
g.drawString("Hello, Java Applet!", 20, 50);
}
}
// HelloApplet.html
<HTML>
<HEAD>
<TITLE> Java Applet </TITLE>
</HEAD>
<BODY>
<APPLET CODE="HelloApplet.class“ WIDTH=200 HEIGHT=100>
</APPLET>
</BODY>
</HTML>
1. 변수와 자료형 개요
Java는 변수를 선언할 때 자료형을 반드시 명시해야 하는 정적 타입 언어이며,
자료형은 기본형(primitive)과 참조형(reference)으로 나뉜다.
2. 기본형(Primitive Type)
| 저장 방식 |
값 자체를 저장 |
| 메모리 위치 |
스택(stack)에 저장 |
| null 허용 |
불가능 |
| 연산 속도 |
빠름 (직접 값 사용) |
| 예시 |
int, double, boolean, char 등 |
총 8가지 기본형:
| 정수형 |
byte, short, int, long |
| 실수형 |
float, double |
| 문자형 |
char |
| 논리형 |
boolean |
3. 참조형(Reference Type)
| 저장 방식 |
객체의 주소(참조값)를 저장 |
| 메모리 위치 |
스택에 주소, 힙에 실제 객체 |
| null 허용 |
가능 |
| 연산 속도 |
상대적으로 느림 (간접 접근) |
| 예시 |
String, 배열, 사용자 정의 클래스 등 |
예시 코드:
String name = "Java";
int[] nums = {1, 2, 3};
4. 기본형 vs 참조형 비교
| 저장 방식 |
값 자체 저장 |
객체의 주소 저장 |
| 메모리 위치 |
스택 |
스택(주소) + 힙(객체) |
| null 허용 |
불가능 |
가능 |
| 사용 예 |
int, double |
String, 배열, 클래스 |
5. Wrapper 클래스
기본형을 객체처럼 다룰 수 있도록 제공되는 클래스.
컬렉션(List, Map)이나 제네릭에서 필요할 때 사용된다.
| int → |
Integer |
| double → |
Double |
| boolean → |
Boolean |
| char → |
Character |
자동 박싱 / 언박싱:
int x = 10;
Integer obj = x; // boxing
int y = obj + 1; // unboxing
6. Wrapper 클래스 주요 메서드
| 문자열 → 숫자 |
Integer.parseInt("123") |
| 숫자 → 문자열 |
Integer.toString(123) |
| 문자열 → 객체 |
Integer.valueOf("456") |
1. 산술 연산자 (Arithmetic Operators)
| + |
덧셈 또는 문자열 연결 |
| - |
뺄셈 |
| * |
곱셈 |
| / |
나눗셈 (정수/정수 = 정수) |
| % |
나머지 |
int a = 10, b = 3;
System.out.println(a / b); // 3
System.out.println(a % b); // 1
2. 대입 연산자 (Assignment Operators)
| = |
값 대입 |
| += |
a += b → a = a + b |
| -= |
a -= b → a = a - b |
| *= |
a *= b → a = a * b |
| /= |
a /= b → a = a / b |
| %= |
a %= b → a = a % b |
3. 비교 연산자 (Relational Operators)
| == |
값이 같은가 |
| != |
값이 다른가 |
| > |
큰가 |
| < |
작은가 |
| >= |
크거나 같은가 |
| <= |
작거나 같은가 |
int x = 5, y = 10;
System.out.println(x == y); // false
4. 논리 연산자 (Logical Operators)
| && |
AND (양쪽 모두 true일 때만 true) |
| ` |
|
| ! |
NOT (반대값) |
boolean a = true, b = false;
System.out.println(a && b); // false
5. 단항 연산자 (Unary Operators)
| + |
양수 표시 (실제 의미 없음) |
| - |
음수 부호 |
| ++ |
1 증가 (전위/후위형) |
| -- |
1 감소 (전위/후위형) |
| ! |
논리 NOT |
| ~ |
비트 반전 (~1010 → 0101) |
int n = 5;
System.out.println(++n); // 6 (전위 증가)
System.out.println(n--); // 6 출력 후 5
6. 조건(삼항) 연산자
| 조건 ? 참 : 거짓 |
조건식이 참이면 앞값, 거짓이면 뒷값을 리턴 |
int max = (a > b) ? a : b;
7. 비트 연산자 (Bitwise Operators)
| & |
AND |
| ` |
` |
| ^ |
XOR |
| ~ |
NOT |
| << |
왼쪽 시프트 (2배씩 증가) |
| >> |
오른쪽 시프트 (2로 나눔) |
| >>> |
부호 없는 오른쪽 시프트 |
int a = 5; // 0101
int b = 3; // 0011
System.out.println(a & b); // 0001 = 1
8. instanceof 연산자
| obj instanceof Class |
obj가 해당 클래스의 인스턴스인지 판별 |
String str = "hello";
System.out.println(str instanceof String); // true
instanceof는 클래스 상속 관계 확인에도 자주 사용된다.
9. 기타 연산자
| new |
객체 생성 |
| [] |
배열 접근 |
| . |
멤버 접근 |
| () |
메서드 호출 |
10. 연산자 우선순위 (상위일수록 우선)
| (), [], ., ++, -- |
최우선 |
| *, /, % |
곱/나눗셈 |
| +, - |
덧셈/뺄셈 |
| <<, >>, >>> |
비트 시프트 |
| <, >, <=, >=, instanceof |
비교 |
| ==, != |
동등 비교 |
| &, ^, ` |
` |
| &&, ` |
|
| ? : |
삼항 연산자 |
| =, += 등 |
대입 연산 |
1. 조건문: if, if-else, else if, switch
if, else if, else
| 형태 |
if (조건) { ... } |
| 의미 |
조건이 true면 실행 |
| else |
조건이 false일 경우 실행 |
| else if |
여러 조건을 연속적으로 검사 |
int score = 85;
if (score >= 90) {
System.out.println("A");
} else if (score >= 80) {
System.out.println("B");
} else {
System.out.println("C");
}
switch 문
| 형태 |
switch (값) { case 값: ... break; } |
| 사용 대상 |
정수형, 문자형, enum, 문자열 (Java 7 이후) |
| 특징 |
여러 조건을 깔끔하게 나눌 때 사용 |
| default |
어느 case도 만족하지 않을 때 실행 |
int day = 2;
switch (day) {
case 1: System.out.println("월"); break;
case 2: System.out.println("화"); break;
default: System.out.println("기타"); break;
}
2. 반복문: for, while, do-while
for 문
| 형태 |
for (초기식; 조건식; 증감식) { ... } |
| 용도 |
반복 횟수가 정해져 있을 때 적합 |
| 특징 |
변수 선언, 조건, 증감 모두 한 줄에 표현 |
for (int i = 0; i < 5; i++) {
System.out.println(i);
}
향상된 for (for-each)
| 형태 |
for (타입 변수 : 배열/컬렉션) { ... } |
| 용도 |
배열, 리스트 등의 모든 요소 순회 |
| 특징 |
인덱스가 필요 없는 반복에 적합 |
int[] nums = {10, 20, 30};
for (int n : nums) {
System.out.println(n);
}
while 문
| 형태 |
while (조건) { ... } |
| 용도 |
조건이 true인 동안 반복 |
| 특징 |
반복 횟수가 정해져 있지 않을 때 사용 |
int i = 0;
while (i < 3) {
System.out.println(i);
i++;
}
do-while 문
| 형태 |
do { ... } while (조건); |
| 특징 |
조건과 상관없이 반드시 한 번 실행 |
| 사용 예 |
메뉴 선택, 사용자 입력 처리 등 |
int i = 0;
do {
System.out.println(i);
i++;
} while (i < 3);
3. 반복 제어문: break, continue
| break |
반복문 전체를 탈출 |
| continue |
현재 반복만 건너뛰고 다음 반복 수행 |
for (int i = 1; i <= 5; i++) {
if (i == 3) continue;
if (i == 5) break;
System.out.println(i);
}
// 출력: 1, 2, 4
4. 요약 정리
| if / else if / else |
조건식 기반 분기 처리 |
| switch |
값에 따른 여러 분기 처리 (정수/문자/문자열) |
| for |
반복 횟수 명확할 때 |
| for-each |
배열, 컬렉션 순회 |
| while |
조건이 true일 때 반복 |
| do-while |
최소 한 번 실행 후 조건 확인 |
| break / continue |
반복 제어용 키워드 |
1. 배열 (Array)
배열 선언과 생성
| 정적 크기 |
한 번 크기 정하면 변경 불가 |
| 인덱스 시작 |
0부터 시작 |
| 선언 방법 |
타입[] 이름 = new 타입[크기]; 또는 {값1, 값2} |
int[] nums = new int[3]; // [0, 0, 0]
String[] names = {"Kim", "Lee", "Park"};
배열 순회
| 일반 for문 |
인덱스 접근 가능 |
| 향상된 for문 |
요소 하나씩 접근 (읽기 전용) |
for (int i = 0; i < nums.length; i++) {
System.out.println(nums[i]);
}
for (String name : names) {
System.out.println(name);
}
배열 주요 속성 및 메서드
| length |
배열의 크기 (속성, 메서드 아님) |
| Arrays.toString(arr) |
배열 전체를 문자열로 출력 |
| Arrays.sort(arr) |
오름차순 정렬 |
| Arrays.copyOf(arr, n) |
앞에서 n개 복사 |
import java.util.Arrays;
int[] arr = {3, 1, 2};
Arrays.sort(arr); // [1, 2, 3]
System.out.println(Arrays.toString(arr));
2. 문자열 (String)
String 클래스
| 불변 (immutable) |
문자열을 수정하면 새로운 객체 생성 |
| 문자열 비교 |
equals()로 비교해야 함 |
| 리터럴 생성 |
"hello"와 같이 직접 사용 가능 |
| new 생성 |
new String("hello") → 힙에 별도 객체 생성 |
String s1 = "Java";
String s2 = new String("Java");
System.out.println(s1.equals(s2)); // true
String 주요 메서드
| length() |
문자열 길이 반환 |
| charAt(i) |
i번째 문자 반환 |
| substring(a, b) |
부분 문자열 반환 |
| indexOf(str) |
str 위치 반환 (없으면 -1) |
| toUpperCase() |
대문자 변환 |
| replace(a, b) |
문자열 교체 |
| split(delimiter) |
구분자로 나누어 배열 반환 |
| trim() |
앞뒤 공백 제거 |
String s = " Hello ";
System.out.println(s.trim().toUpperCase()); // "HELLO"
3. StringBuilder / StringBuffer
항목 | StringBuilder | StringBuffer
| 가변 여부 |
StringBuilder 가변 (mutable) |
StringBuffer 가변 (mutable) |
| 쓰레드 안전성 |
X (단일 쓰레드용) |
O (멀티 쓰레드 안전) |
| 성능 |
더 빠름 |
약간 느림 |
| 사용 시기 |
대부분의 경우 |
동기화가 필요한 경우 |
주요 메서드 (둘 다 동일)
| append(str) |
문자열 끝에 추가 |
| insert(idx, str) |
특정 위치에 삽입 |
| delete(start, end) |
범위 삭제 |
| reverse() |
역순 변환 |
| toString() |
문자열로 변환 |
StringBuilder sb = new StringBuilder("Hi");
sb.append(" Java").insert(0, "Say ").reverse();
System.out.println(sb.toString());
4. 요약 정리
| 배열 |
고정 크기, 같은 타입만 저장, 인덱스로 접근 |
| String |
불변 객체, 문자열 비교는 equals() |
| StringBuilder |
가변 문자열, 성능 좋고 단일 스레드용 |
| StringBuffer |
가변 문자열, 멀티 스레드 안전 |
1. 메서드란?
- 하나의 작업(기능)을 수행하는 코드 블록
- 중복 코드 제거, 재사용, 구조화를 위해 사용됨
형태:
반환형 메서드이름(매개변수 목록) {
실행 코드;
return 반환값;
}
2. 메서드 매개변수(Parameter)
| 정의 |
메서드 호출 시 전달하는 값 (입력값) |
| 선언 위치 |
메서드 소괄호 내부 |
| 개수 |
0개 이상 가능, 자료형 반드시 명시 |
| 전달 방식 |
기본형은 값 복사, 참조형은 주소 복사 |
public void greet(String name) {
System.out.println("Hello, " + name);
}
3. 메서드 반환값(Return)
| 역할 |
메서드 실행 결과를 호출자에게 돌려줌 |
| 키워드 |
return |
| 반환형 |
void(없음) 또는 특정 타입(int, String 등) |
| 주의 |
void가 아닌 경우 반드시 return문 필요 |
public int add(int a, int b) {
return a + b;
}
4. 메서드 예시 모음
// 매개변수 O, 반환값 O
public int multiply(int a, int b) {
return a * b;
}
// 매개변수 O, 반환값 X
public void printName(String name) {
System.out.println(name);
}
// 매개변수 X, 반환값 O
public double getPi() {
return 3.14159;
}
// 매개변수 X, 반환값 X
public void sayHello() {
System.out.println("Hello!");
}
5. 메서드 오버로딩 (Overloading)
| 정의 |
같은 이름의 메서드를 매개변수 형태 다르게 하여 여러 개 정의 |
| 조건 |
메서드 이름 같고, 매개변수의 타입/개수/순서 다름 |
| 리턴 타입만 다른 건 허용 X |
반드시 매개변수가 달라야 함 |
public int sum(int a, int b) {
return a + b;
}
public double sum(double a, double b) {
return a + b;
}
public int sum(int a, int b, int c) {
return a + b + c;
}
6. 오버로딩 요약
| 목적 |
이름은 같지만 다양한 인자를 처리 |
| 예시 |
println(), System.out.print() 모두 오버로딩된 메서드 |
| 특징 |
컴파일러가 인자의 형태를 기준으로 어떤 메서드 호출할지 결정 (정적 바인딩) |
7. 전체 요약
| 매개변수 |
메서드 호출 시 넘기는 값 (입력값) |
| 반환값 |
메서드 실행 후 돌려주는 값 (출력값) |
| 오버로딩 |
같은 이름, 다른 매개변수로 여러 메서드 정의 |
2. 객체지향 프로그래밍(OOP: Object Oriented Programming)
| 클래스와 객체 |
멤버 변수, 생성자, this 키워드 |
| 캡슐화 |
접근 제어자 (public, private, protected, default) |
| 상속과 오버라이딩 |
extends, super(), 메서드 재정의 |
| 다형성 |
업캐스팅, 다운캐스팅, 동적 바인딩 |
| 추상 클래스 & 인터페이스 |
abstract, interface, 설계 원칙 |
| 클래스 상속과 구성 |
"상속" vs "포함" 관계 이해 |
| 내부 클래스 |
정적/비정적 내부 클래스, 익명 클래스 |
1. 클래스(Class)와 객체(Object)
- 클래스는 설계도, 객체는 실체(설계도로 찍어낸 제품).
- 따라서, 클래스가 정의되어 있어야 객체를 생성할 수 있다.
- 클래스를 기반으로 생성된 객체는 속성(멤버 변수)과 행동(메서드)을 가짐
public class Car {
String brand;
void drive() {
System.out.println("Driving...");
}
}
Car c1 = new Car(); // 객체 생성
c1.brand = "Hyundai"; // 멤버 변수 접근
c1.drive(); // 메서드 호출
2. 멤버 변수 (필드)
| 정의 |
클래스 안에 정의된 변수 (객체의 상태 표현) |
| 종류 |
인스턴스 변수, 클래스(static) 변수 |
| 접근 |
객체이름.변수명 으로 접근 |
| 기본값 |
자동 초기화됨 (int → 0, boolean → false 등) |
public class Person {
String name; // 멤버 변수
int age;
}
3. 생성자 (Constructor)
- 기본 생성자(디폴트 생성자): 인자를 가지지 않는 생성자
- 아무 생성자도 정의되지 않았다면 자동으로 만들어짐
class Circle {
int r;
public Circle( ) { // 기본 생성자
r = 0 ;
}
public Circle( int a ) {
r = a;
}
public double getArea( ) {
return r * r *3.14;
}
}
public class CircleArea {
public static void main(String args[ ]) {
Circle b = new Circle( );
Circle c = new Circle( 5 );
System.out.println( b.r + " " + b.getArea( ) );
System.out.println( c.r + " " + c.getArea( ) );
}
}
| 역할 |
객체 생성 시 호출되어 초기화 수행 |
| 이름 |
클래스 이름과 같아야 함 |
| 반환형 |
없음 (void도 안 씀) |
| 오버로딩 |
가능 (매개변수 형태 다르게 여러 개 정의 가능) |
public class Person {
String name;
int age;
// 생성자
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
객체 생성:
Person p = new Person("Kim", 25);
4. this 키워드
- this()도 super()와 같이 블록 처음에 선언해야한다.
| 의미 |
현재 객체 자기 자신을 참조하는 키워드 |
| 용도 |
멤버 변수와 지역 변수 이름이 같을 때 구분 |
| 활용 |
생성자에서 다른 생성자 호출 (this(...)) 가능 |
멤버 변수와 매개변수 구분
public class Person {
String name; // 멤버 변수
public Person(String name) {
this.name = name; // 멤버 변수 = 매개변수
} // this.: 멤버 변수 접근
}
생성자 오버로딩 간 연결
public class Person {
String name;
int age;
public Person(String name) {
this(name, 0); // 다른 생성자 호출
} // this(): 같은 클래스에 있는 다른 생성자를 호출
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
5. 전체 요약
| 클래스 |
객체의 설계도 (속성과 동작 정의) |
| 객체 |
클래스를 기반으로 생성된 실체 |
| 멤버 변수 |
클래스 내부에 선언된 변수 (객체의 상태) |
| 생성자 |
객체 초기화를 위한 특수 메서드 |
| this |
현재 객체 자기 자신 참조, 변수 구분에 사용 |
1. 캡슐화란?
- 객체의 속성(멤버 변수)과 기능(메서드)을 하나로 묶고 외부에서 직접 접근하지 못하도록 감추는 것
- 필드를 private으로 숨기고, getter/setter 메서드로 접근하도록 만든다.
public class User {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
2. 접근 제어자(Access Modifier)
| public |
어디서든 접근 가능 (모든 클래스에서) |
| protected |
같은 패키지 + 상속받은 클래스에서 접근 가능 |
| default (생략됨) |
같은 패키지 내에서만 접근 가능 |
| private |
같은 클래스 내부에서만 접근 가능 (외부 접근 불가) |
3. 접근 제어자 비교 요약
접근 제어자 | 같은 클래스 | 같은 패키지 | 자식 클래스(다른 패키지) | 전체 외부
| public |
O |
O |
O |
O |
| protected |
O |
O |
O |
X |
| default |
O |
O |
X |
X |
| private |
O |
X |
X |
X |
4. 접근 제어자 사용 위치
| 클래스 자체 |
public 또는 default만 사용 가능 |
| 생성자 |
4가지 모두 가능 |
| 멤버 변수 |
보통 private으로 선언 후 getter/setter |
| 메서드 |
클래스 외부 호출이 필요한 경우 public |
5. 캡슐화의 장점
| 데이터 보호 |
객체 외부에서 직접 접근 불가능 |
| 내부 구현 은닉 |
변경 시 외부 코드 영향 줄임 |
| 유지보수 용이 |
인터페이스는 유지, 내부 구현만 수정 가능 |
| 유효성 검사 |
setter에 로직을 넣어 값 제한 가능 |
public void setAge(int age) {
if (age >= 0) this.age = age;
}
6. 전체 요약
| 캡슐화 |
속성과 기능을 하나로 묶고 접근 제어 |
| private |
클래스 내부 전용 (가장 제한적) |
| default |
같은 패키지까지만 접근 가능 |
| protected |
같은 패키지 + 자식 클래스까지 |
| public |
모든 클래스 어디서든 접근 가능 |
1. 상속 (Inheritance)
- 자식 클래스가 부모 클래스의 멤버 변수와 메서드를 물려받는 것
- 고유한 특성을 추가하여 새 클래스를 정의
- 중복 코드를 줄이고, 계층 구조를 설계할 수 있음
- 단일 상속만 지원하지만, interface로 마치 다중 상속된 것처럼 사용할 수 있다.
- extends 키워드 사용
class Animal {
void sound() {
System.out.println("동물 소리");
}
}
class Dog extends Animal {
void bark() {
System.out.println("멍멍");
}
}
2. extends 키워드
| 역할 |
부모 클래스를 상속받을 때 사용 |
| 위치 |
class 자식 extends 부모 |
| 제한 |
Java는 단일 상속만 지원 |
class Cat extends Animal { }
3. 오버라이딩 (Overriding)
- 상속받은 부모 메서드를 자식이 재정의하는 것
- 메서드 이름, 매개변수, 반환형이 완전히 동일해야 함
| 키워드 |
@Override (생략 가능하지만 권장) |
| 목적 |
부모 메서드의 기능을 자식에서 변경 |
| 특징 |
동적 바인딩: 실제 객체 기준으로 메서드 실행됨 |
class Dog extends Animal {
@Override
void sound() {
System.out.println("멍멍!");
}
}
4. super 키워드
- super()도 this()와 같이 블록 처음에 선언해야한다.
| super() |
부모 생성자 호출 |
| super.멤버 |
부모의 변수나 메서드 접근 |
class Animal {
String type = "동물";
Animal() {
System.out.println("Animal 생성자");
}
void sound() {
System.out.println("동물 소리");
}
}
class Dog extends Animal {
Dog() {
super(); // 부모 생성자 호출
System.out.println("Dog 생성자");
}
@Override
void sound() {
super.sound(); // 부모 메서드 호출
System.out.println("멍멍!");
}
}
5. 생성자와 super()
| 목적 |
부모 클래스의 생성자를 명시적으로 호출 |
| 위치 |
자식 생성자의 첫 줄에서만 호출 가능 |
| 생략 시 |
super()는 자동으로 컴파일러가 추가 |
6. 상속 & 오버라이딩 비교
| 상속 |
부모의 모든 public/protected 멤버를 자식이 사용 |
| 오버라이딩 |
부모의 메서드를 자식이 재정의함 |
| super |
부모의 생성자/메서드/변수를 참조할 때 사용 |
| @Override |
오버라이딩 의도 명시 + 컴파일 오류 방지 |
7. 전체 요약
| extends |
부모 클래스 상속 |
| @Override |
오버라이딩 명시 |
| super() |
부모 생성자 호출 |
| super.메서드 |
부모 메서드 호출 |
| 오버라이딩 |
자식이 부모의 메서드를 재정의 |
| 동적 바인딩 |
실제 객체 타입 기준으로 메서드 실행됨 |
1. 다형성(Polymorphism)이란?
- 부모 타입의 참조 변수로 여러 자식 객체를 가리킬 수 있는 것
- 하나의 메서드가 여러 동작(형태)을 갖는 것 (오버라이딩 포함)
다형성의 핵심 효과
| 코드의 유연성 |
부모 타입으로 여러 자식 객체 처리 가능 |
| 코드의 재사용성 |
같은 인터페이스/부모 클래스로 다양한 구현 가능 |
| 동적 바인딩 |
실행 시점에 호출 메서드가 결정됨 (오버라이딩된 메서드 실행) |
2. 업캐스팅 (Upcasting)
- 자식 객체를 부모 타입 변수에 저장하는 것
- 자동 형변환이므로 명시적 캐스팅 필요 없음
| 정의 |
자식 → 부모 타입으로 변환 |
| 문법 |
Parent p = new Child(); |
| 사용 이유 |
하나의 참조 변수로 여러 객체를 다루기 위해 |
class Animal {
void sound() { System.out.println("Animal sound"); }
}
class Cat extends Animal {
void sound() { System.out.println("Meow"); }
}
Animal a = new Cat(); // 업캐스팅
a.sound(); // 출력: Meow (동적 바인딩)
3. 다운캐스팅 (Downcasting)
- 부모 타입의 객체를 자식 타입으로 형변환
- 반드시 명시적 형변환 필요
| 정의 |
부모 → 자식 타입으로 변환 |
| 문법 |
Child c = (Child) parentObj; |
| 위험 |
형변환 실패 시 ClassCastException 발생 가능 |
| 안전 캐스팅 |
instanceof로 타입 검사 후 캐스팅 권장 |
Animal a = new Cat(); // 업캐스팅
Cat c = (Cat) a; // 다운캐스팅
c.sound(); // Meow
if (a instanceof Cat) {
Cat safeCat = (Cat) a;
}
4. 동적 바인딩 (Dynamic Binding)
- 실행 시간(runtime)에 실제 객체의 메서드가 결정되어 실행됨
- 부모 타입으로 호출하더라도 자식 클래스에서 오버라이딩한 메서드가 실행됨
| 적용 대상 |
오버라이딩된 메서드 (인스턴스 메서드만 해당) |
| 발생 시점 |
컴파일 시점에 선언된 타입, 실행 시점에 실제 타입 따라 결정 |
| 의미 |
자식 클래스의 기능을 부모 참조 변수로 사용할 수 있음 |
Animal a = new Cat();
a.sound(); // 컴파일 시점엔 Animal의 sound(), 실행 시점엔 Cat의 sound()
5. 다형성 전체 구조 예제
class Animal {
void sound() { System.out.println("Animal sound"); }
}
class Dog extends Animal {
@Override
void sound() { System.out.println("Woof"); }
}
class Cat extends Animal {
@Override
void sound() { System.out.println("Meow"); }
}
public class Test {
public static void main(String[] args) {
Animal a1 = new Dog(); // 업캐스팅
Animal a2 = new Cat(); // 업캐스팅
a1.sound(); // Woof (동적 바인딩)
a2.sound(); // Meow
// 다운캐스팅
if (a2 instanceof Cat) {
Cat c = (Cat) a2;
c.sound();
}
}
}
6. 요약 정리
| 업캐스팅 |
자식 → 부모, 자동 형변환, 다형성의 기본 |
| 다운캐스팅 |
부모 → 자식, 명시적 형변환, instanceof 권장 |
| 동적 바인딩 |
실행 시점에 실제 객체 기준으로 메서드 호출 |
| 다형성 |
하나의 참조 타입으로 다양한 객체를 다루는 능력 |
1. 추상 클래스 (abstract class)
- 추상 클래스를 상속 받은 자식 클래스에서 반드시 재정의 해야하므로 (재정의 할 수 없는) final과 함께 사용할 수 없다.
- 일반 클래스로 지정 시 상속받은 자식 클래스에서 재정의하지 않아도 컴파일 오류가 생기지 않는다. 이것을 방지하기 위해 추상 클래스로 지정한다.
- 이름만 있고 내용이 없다.
| 정의 |
하나 이상의 추상 메서드를 포함한 클래스 (객체 생성 불가) |
| 키워드 |
abstract class 클래스명 |
| 사용 목적 |
공통된 기능을 가진 클래스의 부모 역할 |
| 생성자 |
가질 수 있음 (자식 생성 시 호출됨) |
| 멤버 |
일반 변수/메서드, 추상 메서드 모두 가질 수 있음 |
abstract class Animal { // 추상 메서드가 있으므로 추상 클래스로 지정해야함
String name;
abstract void sound(); // 추상 메서드
void eat() {
System.out.println("먹는다");
}
}
2. 인터페이스 (interface)
- 이름만 존재하고 내용(블록)이 없다.
- 상속과 같은 기능을 사용하지는 않으면서, 서로 관련이 없는 다른 클래스가 갖는 공통적인 기능을 하나의 인터페이스로 정의한다.
- 인터페이스는 다중 상속이 가능하다.
| 정의 |
100% 추상 메서드로 구성된 타입(자바 8 이전 기준) |
| 키워드 |
interface 인터페이스명 |
| 사용 목적 |
기능 명세서 역할, 다양한 클래스에 기능 부여 |
| 멤버 |
모든 필드는 public static final, 모든 메서드는 public abstract (생략 가능) |
interface Soundable {
void sound(); // public abstract 생략됨
}
3. 공통점
| 직접 객체 생성 불가 |
반드시 자식 클래스가 구현 후 사용 |
| 다형성 사용 가능 |
부모 타입으로 자식 객체 참조 가능 |
| 메서드 규격 제공 |
자식 클래스가 구현할 규약(형식)을 정의 |
4. 차이점 비교
추상 | 클래스 | 인터페이스
| 키워드 |
abstract class |
interface |
| 다중 상속 |
❌ (단일 상속만 가능) |
✅ (여러 인터페이스 구현 가능) |
| 멤버 구성 |
변수, 생성자, 일반 메서드, 추상 메서드 |
상수 + 추상 메서드 (default, static 가능) |
| 목적 |
공통 로직 제공 |
기능 명세 제공 |
| 접근제어자 |
자유롭게 가능 |
모든 멤버는 public |
| 용도 |
상속 계층 구조 설계 |
협업/기능 명세 전달 목적 |
5. 구현 vs 상속
| 추상 클래스 상속 |
class 자식 extends 부모추상클래스 |
| 인터페이스 구현 |
class 자식 implements 인터페이스 |
class Cat extends Animal implements Soundable {
@Override
void sound() { System.out.println("야옹"); }
}
6. default, static 메서드 (Java 8+)
인터페이스에서도 구현된 메서드 작성 가능!
| default 메서드 |
인스턴스 메서드에 기본 구현 제공 |
| static 메서드 |
클래스명으로 호출 가능 (유틸용) |
interface Greeting {
default void sayHi() {
System.out.println("안녕!");
}
static void guide() {
System.out.println("인사 방법 안내");
}
}
7. 설계 원칙 (언제 어떤 걸 쓸까?)
| 공통 기능 + 기본 구현 필요 |
추상 클래스 (코드 재사용) |
| 다양한 클래스에 공통 기능 제공 |
인터페이스 (기능 명세, 다중 구현) |
| 객체끼리 is-a 관계 |
추상 클래스 (상속 구조) |
| 기능 중심 연결(has-a 관계) |
인터페이스 (역할 부여) |
8. 전체 요약 정리
| 추상 클래스 |
is-a 상속 관계, 코드 재사용 목적 |
| 인터페이스 |
다중 구현 가능, 기능 명세 목적 |
| 공통점 |
객체 생성 불가, 구현 강제, 다형성 활용 |
| 설계 기준 |
구현 공유 = 추상 클래스 / 기능 공유 = 인터페이스 |
1. 개념 차이: 상속 vs 구성
| 상속 (Inheritance) |
한 클래스가 다른 클래스를 확장하여 코드를 물려받는 것 |
| 구성 (Composition) |
한 클래스가 다른 클래스를 내부에 포함하여 기능을 위임하는 것 |
2. 상속 (is-a 관계)
| 관계 의미 |
자식 클래스는 부모 클래스의 일종이다 (is-a) |
| 키워드 |
extends 사용 |
| 특징 |
코드 재사용이 용이하나, 부모 클래스에 강하게 의존 |
class Animal {
void move() { System.out.println("움직임"); }
}
class Dog extends Animal {
void bark() { System.out.println("멍멍"); }
}
// Dog is-a Animal → OK
3. 구성 (has-a 관계)
| 관계 의미 |
클래스가 다른 클래스를 멤버 변수로 가짐 (has-a) |
| 키워드 |
new 키워드로 포함 객체 생성 |
| 특징 |
결합도 낮음, 유연한 구조 설계 가능 (권장되는 방식) |
class Engine {
void start() { System.out.println("엔진 시동"); }
}
class Car {
private Engine engine = new Engine(); // 포함 관계
void drive() {
engine.start();
System.out.println("주행 중");
}
}
// Car has-a Engine → OK
4. 비교 요약
상속 (is-a) | 구성 (has-a)
| 관계 |
자식 is-a 부모 |
A has-a B |
| 키워드 |
extends |
내부 필드로 포함 |
| 결합도 |
높음 (강결합) |
낮음 (약결합) |
| 유연성 |
낮음 (변경 어려움) |
높음 (유지보수 유리) |
| 다중구현 |
단일 상속만 가능 |
다중 포함 가능 |
| 예시 |
class Dog extends Animal |
class Car { Engine engine; } |
5. 설계 원칙: 언제 상속, 언제 구성?
| 자식이 부모의 일종인 경우 |
상속 (ex. Student is-a Person) |
| 단지 기능을 사용하고 싶은 경우 |
구성 (ex. Car has-a Engine) |
| 코드 재사용만 목적 |
구성이 더 좋음 (상속은 계층관계 명확할 때만) |
| 클래스 변경 가능성 있음 |
구성이 유리 (상속은 부모 변경 시 영향 큼) |
6. 실전 예시
상속:
class Printer {
void print() { System.out.println("기본 출력"); }
}
class ColorPrinter extends Printer {
void colorPrint() { System.out.println("컬러 출력"); }
}
구성:
class Printer {
void print() { System.out.println("기본 출력"); }
}
class Report {
private Printer printer = new Printer();
void generate() {
printer.print(); // 내부 기능 위임
}
}
7. 전체 요약
| 상속 |
부모 클래스를 확장해 자식이 코드 물려받음 (is-a) |
| 구성 |
클래스가 다른 클래스를 내부에 포함 (has-a) |
| 권장 설계 |
가능하면 구성 → 더 유연하고 안전한 설계 방식 |
| 선택 기준 |
계층 구조 명확하면 상속, 아니면 구성 우선 고려 |
1. 내부 클래스란?
- 클래스 안에 정의된 클래스
- 외부 클래스의 멤버(변수/메서드)에 접근할 수 있음
- 주로 캡슐화, 외부 클래스와 긴밀한 연관 구조에 사용됨
2. 내부 클래스의 종류
| 인스턴스 내부 클래스 |
외부 클래스의 인스턴스에 종속됨 |
| 정적(static) 내부 클래스 |
외부 클래스의 인스턴스와 무관함 |
| 지역 내부 클래스 |
메서드 안에 정의된 클래스 |
| 익명 클래스 |
이름이 없는 일회성 클래스 (주로 인터페이스 구현 시 사용) |
3. 비정적 내부 클래스 (인스턴스 내부 클래스)
| 선언 위치 |
외부 클래스의 멤버 영역 |
| 외부 클래스와 관계 |
외부 클래스 객체가 있어야 사용 가능 |
| 외부 멤버 접근 |
모든 멤버에 직접 접근 가능 (private 포함) |
class Outer {
int outerNum = 10;
class Inner {
void display() {
System.out.println("Outer Num = " + outerNum);
}
}
}
// 사용
Outer outer = new Outer();
Outer.Inner inner = outer.new Inner();
inner.display();
4. 정적 내부 클래스 (static inner class)
| 선언 위치 |
외부 클래스의 멤버 영역에 static으로 선언 |
| 외부 클래스와 관계 |
외부 인스턴스 없이 사용 가능 |
| 접근 제한 |
외부 클래스의 static 멤버만 직접 접근 가능 |
class Outer {
static int staticNum = 20;
static class StaticInner {
void display() {
System.out.println("Static Num = " + staticNum);
}
}
}
// 사용
Outer.StaticInner si = new Outer.StaticInner();
si.display();
5. 지역 내부 클래스
| 선언 위치 |
메서드나 블록 안에서 선언 |
| 유효 범위 |
선언된 메서드 내부에서만 사용 가능 |
| 특징 |
메서드의 지역변수를 사용할 수 있지만, 해당 변수는 final 또는 effectively final이어야 함 |
class Outer {
void method() {
int x = 5; // effectively final
class LocalInner {
void print() {
System.out.println("x = " + x);
}
}
LocalInner li = new LocalInner();
li.print();
}
}
6. 익명 클래스 (Anonymous Class)
| 특징 |
이름이 없는 일회성 클래스 |
| 용도 |
인터페이스/추상 클래스의 일시적 구현 객체 생성 시 사용 |
| 문법 |
new 인터페이스명() { 구현 } 또는 new 클래스명() { 재정의 } |
인터페이스 구현
interface Greeting {
void sayHi();
}
Greeting g = new Greeting() {
@Override
public void sayHi() {
System.out.println("안녕!");
}
};
g.sayHi();
추상 클래스 상속
abstract class Animal {
abstract void sound();
}
Animal a = new Animal() {
void sound() {
System.out.println("어흥!");
}
};
a.sound();
7. 비교 요약
클래스 | 선언 위치 | 외부 인스턴스 필요 | 특징
| 인스턴스 내부 클래스 |
클래스 멤버 |
필요 |
외부 객체와 밀접한 관계 |
| 정적 내부 클래스 |
클래스 멤버 (static) |
불필요 |
외부 객체와 무관한 구조 |
| 지역 내부 클래스 |
메서드 안 |
메서드 내에서만 사용 |
특정 메서드 전용 |
| 익명 클래스 |
메서드 안 |
즉석에서 생성 |
리스너, 콜백, 람다 대용 |
8. 전체 요약
| 내부 클래스 |
외부 클래스에 포함된 클래스 |
| 인스턴스 내부 클래스 |
외부 인스턴스 필요, 멤버 직접 접근 가능 |
| 정적 내부 클래스 |
외부 인스턴스 필요 없음, static 멤버만 접근 |
| 지역 클래스 |
메서드 안에서만 사용 |
| 익명 클래스 |
이름 없이 일회성 구현, 보통 인터페이스/추상클래스 구현용 |
3단계. Java 핵심 API & 도구 활용 능력
| java.lang |
Object, String, Math, Wrapper 클래스 |
| java.util |
ArrayList, HashMap, Iterator, Collections |
| java.time |
LocalDate, LocalTime, DateTimeFormatter |
| java.io |
File, InputStream, Reader, BufferedReader |
| java.nio |
Path, Files, Channels, WatchService |
| java.net |
URL 연결, TCP/UDP 통신 개요 |
| java.util.regex |
정규 표현식 처리 |
1. java.lang.Object
- 모든 클래스의 최상위 부모 클래스
- 자바에서 모든 객체는 Object를 상속받음
- 공통 메서드를 오버라이딩하여 커스터마이징 가능
| toString() |
객체를 문자열로 표현 (클래스명@해시코드) → 보통 오버라이딩 |
| equals(Object obj) |
두 객체가 같은지 비교 (주소 비교 → 오버라이딩 시 내용 비교 가능) |
| hashCode() |
객체의 해시 코드 값 반환 (equals와 함께 사용됨) |
| getClass() |
객체의 런타임 클래스 정보를 반환 |
| clone() |
객체 복제 |
| finalize() |
객체가 GC 대상 될 때 호출됨 (사용 비권장) |
public class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return "이름: " + name;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof Person) {
return this.name.equals(((Person) obj).name);
}
return false;
}
}
2. String 클래스 (불변 객체)
- 문자열을 표현하는 클래스
- 불변 객체 (한번 생성된 문자열은 변경 불가)
- 문자열 비교 시 equals()를 사용해야 함 (==은 주소 비교)
| length() |
문자열 길이 반환 |
| charAt(int index) |
특정 인덱스의 문자 반환 |
| substring(a, b) |
부분 문자열 반환 |
| indexOf(String) |
위치 찾기 (-1이면 없음) |
| equals() / equalsIgnoreCase() |
문자열 비교 |
| replace(), split(), toUpperCase() |
치환, 분할, 대문자 변환 등 |
| trim() |
앞뒤 공백 제거 |
String s = " Hello Java ";
System.out.println(s.trim().toUpperCase()); // "HELLO JAVA"
3. Math 클래스
- 수학 계산 관련 static 메서드 모음
- 객체 생성 없이 Math.메서드() 형태로 사용
| Math.abs(x) |
절댓값 |
| Math.max(a, b) / min(a, b) |
최대값/최소값 |
| Math.pow(a, b) |
a^b |
| Math.sqrt(x) |
제곱근 |
| Math.random() |
0.0 이상 1.0 미만의 난수 반환 |
| Math.round() / ceil() / floor() |
반올림, 올림, 내림 |
int max = Math.max(10, 20); // 20
double r = Math.random(); // 0.0 <= r < 1.0
long round = Math.round(3.7); // 4
4. Wrapper 클래스
- 기본형 → 참조형으로 포장한 클래스
- 제네릭, 컬렉션 등에서 기본형을 객체처럼 사용 가능
- 자동 박싱/언박싱 지원 (Java 5 이후)
기본형 Wrapper 클래스
| int |
Integer |
| double |
Double |
| char |
Character |
| boolean |
Boolean |
| byte |
Byte |
| short |
Short |
| long |
Long |
| float |
Float |
주요 기능
| parseInt(String) |
문자열 → 기본형 |
| valueOf(String) |
문자열 → Wrapper 객체 |
| xxxValue() |
Wrapper → 기본형 |
| compareTo() |
값 비교 |
| equals() |
객체 값 비교 |
Integer i = Integer.valueOf("123"); // 문자열 → Integer
int n = Integer.parseInt("456"); // 문자열 → int
int x = i.intValue(); // 언박싱
5. 요약 정리
| Object |
모든 클래스의 조상, toString(), equals(), hashCode() 오버라이딩 대상 |
| String |
불변 객체, 다양한 문자열 처리 메서드 제공 |
| Math |
수학 계산용 정적 메서드 클래스 |
| Wrapper |
기본형을 객체로 다루기 위한 클래스, 제네릭과 함께 자주 사용됨 |
1. ArrayList (동적 배열)
| 설명 |
배열 기반의 리스트. 크기가 자동으로 늘어남 |
| 특징 |
순서 유지, 중복 허용, 인덱스로 접근 가능 |
| 선언 |
List<String> list = new ArrayList<>(); |
| 주요 메서드 |
add(), get(), remove(), size(), contains() |
ArrayList<String> fruits = new ArrayList<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple"); // 중복 허용
System.out.println(fruits.get(1)); // Banana
System.out.println(fruits.contains("Apple")); // true
2. HashMap (키-값 쌍 저장)
| 설명 |
키와 값을 쌍으로 저장하는 자료구조 |
| 특징 |
키는 중복 X, 값은 중복 O |
| 선언 |
Map<String, Integer> map = new HashMap<>(); |
| 주요 메서드 |
put(), get(), remove(), containsKey(), keySet(), values(), entrySet() |
HashMap<String, Integer> map = new HashMap<>();
map.put("Kim", 90);
map.put("Lee", 85);
map.put("Kim", 95); // 기존 키 값 덮어씀
System.out.println(map.get("Kim")); // 95
System.out.println(map.containsKey("Lee")); // true
3. Iterator (반복자)
| 설명 |
컬렉션을 순회할 수 있는 객체 |
| 선언 |
Iterator<String> it = list.iterator(); |
| 주요 메서드 |
hasNext(), next(), remove() |
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
Iterator<String> it = list.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
- Iterator는 모든 Collection에서 사용 가능
- List나 Set, Map.keySet() 등을 순회할 때 사용
4. Collections 클래스 (도우미 유틸)
| 설명 |
컬렉션을 다룰 때 유용한 static 메서드 제공 |
| 주요 기능 |
정렬, 최소/최대, 뒤집기, 섞기 등 |
| 사용 예 |
Collections.sort(list) |
ArrayList<Integer> nums = new ArrayList<>();
nums.add(3); nums.add(1); nums.add(2);
Collections.sort(nums); // 오름차순
Collections.reverse(nums); // 내림차순
int max = Collections.max(nums); // 최대값
5. 전체 요약 비교
| 리스트 |
ArrayList |
순서 유지, 인덱스로 접근, 중복 허용 |
| 맵 |
HashMap |
키-값 구조, 키 중복 불가 |
| 반복자 |
Iterator |
컬렉션 순회용 객체 (hasNext(), next()) |
| 유틸 |
Collections |
컬렉션 정렬, 최대/최소, 셔플 등 제공 |
6. 활용 예제 통합
List<String> names = new ArrayList<>();
names.add("Tom");
names.add("Jane");
names.add("Alice");
Collections.sort(names); // 알파벳순 정렬
for (String name : names) {
System.out.println(name);
}
Map<String, Integer> scoreMap = new HashMap<>();
scoreMap.put("Tom", 90);
scoreMap.put("Jane", 85);
for (String key : scoreMap.keySet()) {
System.out.println(key + ": " + scoreMap.get(key));
}
1. java.time 패키지란?
- Java 8부터 도입된 날짜와 시간 처리 전용 패키지
- 기존 Date, Calendar의 복잡함과 오류 가능성을 개선
- 불변(immutable) 객체 기반 → 쓰레드 안전
2. LocalDate
| 설명 |
날짜(연, 월, 일)만 표현 (시간 없음) |
| 특징 |
불변 객체 |
| 사용 |
LocalDate.now(), LocalDate.of(2025, 4, 5) 등 |
LocalDate today = LocalDate.now(); // 현재 날짜
LocalDate birth = LocalDate.of(2000, 1, 15); // 지정 날짜
LocalDate nextWeek = today.plusWeeks(1); // 일주일 후
| getYear(), getMonth(), getDayOfMonth() |
년/월/일 반환 |
| plusDays(), minusDays() |
날짜 연산 |
| isAfter(), isBefore() |
날짜 비교 |
3. LocalTime
| 설명 |
시간(시, 분, 초, 나노초)만 표현 (날짜 없음) |
| 특징 |
불변 객체 |
| 사용 |
LocalTime.now(), LocalTime.of(14, 30) 등 |
LocalTime now = LocalTime.now(); // 현재 시간
LocalTime time = LocalTime.of(15, 45); // 15:45
LocalTime plusHour = time.plusHours(2); // 17:45
| getHour(), getMinute(), getSecond() |
시/분/초 반환 |
| isAfter(), isBefore() |
시간 비교 |
| plusMinutes(), minusHours() |
시간 연산 |
4. DateTimeFormatter
| 설명 |
날짜/시간을 문자열로 포맷하거나, 문자열을 파싱할 때 사용 |
| 사용 |
DateTimeFormatter.ofPattern("yyyy-MM-dd") |
날짜를 문자열로
LocalDate date = LocalDate.of(2025, 4, 5);
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy.MM.dd");
String formatted = date.format(fmt); // "2025.04.05"
문자열을 날짜로
String input = "2025/04/05";
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy/MM/dd");
LocalDate parsed = LocalDate.parse(input, fmt);
포맷 기호
| yyyy |
연도 |
| MM |
월 (2자리) |
| dd |
일 (2자리) |
| HH |
시 (24시간) |
| mm |
분 |
| ss |
초 |
5. 전체 요약
| LocalDate |
날짜만 표현 (연, 월, 일) |
| LocalTime |
시간만 표현 (시, 분, 초) |
| DateTimeFormatter |
날짜/시간 ↔ 문자열 포맷/파싱 클래스 |
6. 종합 예제
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
public class DateTimeExample {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
LocalTime now = LocalTime.now();
DateTimeFormatter dateFmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("HH:mm:ss");
System.out.println("오늘 날짜: " + today.format(dateFmt));
System.out.println("현재 시간: " + now.format(timeFmt));
}
}
1. File 클래스
| 설명 |
실제 파일이나 디렉토리를 나타내는 클래스 (입출력 기능은 없음) |
| 역할 |
파일/폴더 생성, 삭제, 정보 조회 등 |
| 생성 |
File file = new File("경로") |
주요 메서드
| exists() |
존재 여부 확인 |
| isFile(), isDirectory() |
파일/디렉토리 여부 |
| createNewFile() |
새 파일 생성 |
| mkdir(), mkdirs() |
폴더 생성 (단일/중첩) |
| delete() |
삭제 |
| getName(), getPath() |
이름, 경로 반환 |
File file = new File("test.txt");
if (!file.exists()) {
file.createNewFile(); // 파일 생성
}
System.out.println("파일 이름: " + file.getName());
2. InputStream (바이트 기반 입력)
| 설명 |
바이트(byte) 단위로 데이터를 읽는 추상 클래스 |
| 하위 클래스 |
FileInputStream, ByteArrayInputStream 등 |
| 주요 메서드 |
read(), read(byte[]), close() |
텍스트 파일에서 바이트 읽기
FileInputStream fis = new FileInputStream("test.txt");
int data;
while ((data = fis.read()) != -1) {
System.out.print((char) data);
}
fis.close();
- 텍스트도 읽을 수 있지만 문자 인코딩 문제 발생 가능
- 이미지, 음악, 영상 등 이진 파일에 적합
3. Reader (문자 기반 입력)
| 설명 |
문자(char) 단위로 데이터를 읽는 추상 클래스 |
| 하위 클래스 |
FileReader, InputStreamReader 등 |
| 주요 메서드 |
read(), read(char[]), close() |
문자 기반으로 텍스트 파일 읽기
FileReader fr = new FileReader("test.txt");
int ch;
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
fr.close();
- 텍스트 파일 전용
- 자동으로 인코딩 처리 (UTF-8, MS949 등 지정도 가능)
4. BufferedReader (버퍼링 문자 입력)
| 설명 |
Reader에 버퍼 기능 추가하여 효율적인 입력 지원 |
| 생성 방식 |
BufferedReader br = new BufferedReader(new FileReader(...)) |
| 주요 메서드 |
readLine(), read(), close() |
텍스트 파일에서 한 줄씩 읽기
BufferedReader br = new BufferedReader(new FileReader("test.txt"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
br.close();
- readLine() 메서드로 줄 단위 입력 가능
- 텍스트 처리 시 가장 많이 쓰이는 클래스 중 하나
5. 전체 비교 요약
클래스 | 기반 | 용도 | 특징
| File |
파일 자체 |
파일/디렉토리 관리 |
입출력 불가 |
| InputStream |
바이트 |
이진 파일 읽기 |
추상 클래스 |
| Reader |
문자 |
문자 스트림 입력 |
추상 클래스 |
| BufferedReader |
문자 + 버퍼 |
텍스트 파일 효율적 읽기 |
readLine() 지원 |
6. 통합 예제
import java.io.*;
public class FileReadExample {
public static void main(String[] args) {
File file = new File("sample.txt");
try (BufferedReader br = new BufferedReader(new FileReader(file))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println("읽은 줄: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
1. Path (java.nio.file.Path)
| 설명 |
파일이나 디렉터리의 경로를 객체로 표현 |
| 생성 |
Path path = Paths.get("data/file.txt"); |
| 특징 |
File 클래스보다 다양한 경로 조작 메서드 제공 |
주요 메서드
| getFileName() |
마지막 파일/디렉터리 이름 반환 |
| getParent() |
부모 경로 반환 |
| getRoot() |
루트 디렉터리 반환 |
| toAbsolutePath() |
절대 경로 반환 |
| resolve("자식경로") |
하위 경로 연결 |
| normalize() |
경로 정리 (.., . 등 제거) |
Path path = Paths.get("docs", "example.txt");
System.out.println(path.toAbsolutePath());
2. Files (java.nio.file.Files)
| 설명 |
Path 기반 파일 조작을 위한 static 유틸 메서드 제공 |
| 특징 |
파일 복사, 삭제, 읽기, 쓰기, 생성 등 전반적 지원 |
주요 메서드
| exists(path) |
경로 존재 여부 확인 |
| createFile(path) |
새 파일 생성 |
| delete(path) |
파일 삭제 |
| copy(src, dest) |
파일 복사 |
| move(src, dest) |
파일 이동/이름 변경 |
| readAllLines(path) |
모든 줄 읽어서 List 반환 |
| write(path, List<String>) |
줄 단위로 쓰기 |
Path file = Paths.get("test.txt");
if (!Files.exists(file)) {
Files.createFile(file);
}
Files.write(file, List.of("Hello", "Java NIO"));
List<String> lines = Files.readAllLines(file);
lines.forEach(System.out::println);
3. Channels (java.nio.channels)
| 설명 |
파일, 네트워크 등의 입출력을 빠르게 처리하는 스트림 대체 객체 |
| 특징 |
고성능 입출력, 버퍼 기반, 양방향 지원, 멀티스레드 안전 |
주요 클래스
| FileChannel |
파일을 읽거나 쓸 수 있는 채널 |
| ReadableByteChannel, WritableByteChannel |
입력/출력 전용 채널 |
| Channels.newChannel(InputStream) |
기존 스트림을 채널로 변환 가능 |
주요 메서드
| read(ByteBuffer) |
채널에서 버퍼로 읽기 |
| write(ByteBuffer) |
버퍼에서 채널로 쓰기 |
| position(), truncate(), size() |
포지션, 크기 제어 |
Path path = Paths.get("nio_data.txt");
FileChannel channel = FileChannel.open(path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
ByteBuffer buffer = ByteBuffer.wrap("Hello NIO Channel!".getBytes());
channel.write(buffer);
channel.close();
4. WatchService (java.nio.file.WatchService)
| 설명 |
디렉터리/파일의 변화를 감지하는 감시 서비스 |
| 사용 목적 |
디렉터리에 대한 생성/수정/삭제 이벤트 감시 |
주요 이벤트
| ENTRY_CREATE |
파일 생성 |
| ENTRY_MODIFY |
파일 수정 |
| ENTRY_DELETE |
파일 삭제 |
사용 흐름 | 단계
| WatchService ws = FileSystems.getDefault().newWatchService() |
서비스 생성 |
| path.register(ws, 이벤트...) |
감시 등록 |
| ws.take() |
이벤트 발생 시까지 대기 |
| watchKey.pollEvents() |
이벤트 확인 |
Path path = Paths.get("logs");
WatchService ws = FileSystems.getDefault().newWatchService();
path.register(ws, StandardWatchEventKinds.ENTRY_CREATE);
while (true) {
WatchKey key = ws.take(); // 블로킹
for (WatchEvent<?> event : key.pollEvents()) {
System.out.println("이벤트 발생: " + event.kind());
System.out.println("파일 이름: " + event.context());
}
key.reset(); // 다시 감시 시작
}
5. 비교 요약
클래스 | 설명 | 주요 기능
| Path |
파일 경로 객체 |
문자열 경로보다 안전하게 조작 가능 |
| Files |
Path 기반 유틸 |
복사, 삭제, 쓰기 등 파일 전반 조작 |
| Channels |
고성능 입출력 |
버퍼 기반, 멀티스레드 환경에 강함 |
| WatchService |
파일 시스템 감시 |
실시간 이벤트 감지 (파일 생성 등) |
6. 사용 추천 클래스
| 경로를 객체로 관리하고 싶다 |
Path |
| 파일을 만들거나 삭제하고 싶다 |
Files |
| 대용량 파일을 고속 입출력하고 싶다 |
FileChannel |
| 폴더 감시 시스템을 만들고 싶다 |
WatchService |
1. URL 연결 (java.net.URL)
| 설명 |
웹 주소(프로토콜 + 경로 + 포트 등)를 객체로 표현 |
| 클래스 |
java.net.URL |
| 주요 기능 |
웹 페이지 연결, 이미지/파일 다운로드, InputStream으로 읽기 등 |
주요 메서드
| openStream() |
해당 URL의 데이터 스트림 반환 |
| getProtocol() |
프로토콜 반환 (http, https 등) |
| getHost(), getPort() |
호스트 이름, 포트 번호 반환 |
| getPath(), getQuery() |
경로, 쿼리 문자열 반환 |
import java.net.*;
import java.io.*;
public class UrlReadExample {
public static void main(String[] args) throws Exception {
URL url = new URL("https://www.example.com");
BufferedReader br = new BufferedReader(
new InputStreamReader(url.openStream())
);
String line;
while ((line = br.readLine()) != null) {
System.out.println(line); // 웹 페이지 HTML 출력
}
br.close();
}
}
2. TCP 통신 (신뢰성 있는 연결 지향 방식)
| 설명 |
연결(Connect) 후 데이터를 주고받는 통신 방식 |
| 특징 |
신뢰성 높음 (순서보장, 오류검출, 패킷재전송) |
| 사용 클래스 |
ServerSocket, Socket |
클래스
| ServerSocket |
서버 소켓 생성 (포트 대기) |
| Socket |
클라이언트/서버 양쪽에서 데이터 송수신 |
TCP 서버
ServerSocket server = new ServerSocket(8888);
Socket client = server.accept(); // 클라이언트 접속 대기
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String msg = in.readLine();
System.out.println("클라이언트로부터: " + msg);
in.close();
client.close();
server.close();
TCP 클라이언트
Socket socket = new Socket("localhost", 8888);
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
out.write("Hello Server\n");
out.flush();
out.close();
socket.close();
3. UDP 통신 (비연결 지향, 빠름)
| 설명 |
연결 없이 데이터를 빠르게 주고받는 방식 |
| 특징 |
빠르지만 신뢰성 낮음 (순서 보장 X, 유실 가능) |
| 사용 클래스 |
DatagramSocket, DatagramPacket |
UDP 서버
DatagramSocket server = new DatagramSocket(9999);
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
server.receive(packet); // 수신 대기
String msg = new String(packet.getData(), 0, packet.getLength());
System.out.println("받은 메시지: " + msg);
server.close();
UDP 클라이언트
DatagramSocket client = new DatagramSocket();
InetAddress addr = InetAddress.getByName("localhost");
byte[] msg = "Hi UDP".getBytes();
DatagramPacket packet = new DatagramPacket(msg, msg.length, addr, 9999);
client.send(packet);
client.close();
4. TCP vs UDP 비교 요약
항목 | TCP | UDP
| 연결 방식 |
연결 지향 (3-way handshake) |
비연결 지향 |
| 신뢰성 |
높음 (패킷 순서 보장, 재전송) |
낮음 (유실 가능) |
| 속도 |
느림 |
빠름 |
| 대표 클래스 |
Socket, ServerSocket |
DatagramSocket, DatagramPacket |
| 사용 예 |
웹 서버, 파일 전송, DB 연결 |
실시간 게임, 영상 스트리밍 |
5. 전체 요약
주제 | 주요 클래스 | 용도
| URL 연결 |
URL, URLConnection |
웹 페이지 데이터 읽기 |
| TCP |
Socket, ServerSocket |
신뢰성 있는 통신 (양방향) |
| UDP |
DatagramSocket, DatagramPacket |
빠르고 간단한 단방향 메시지 전송 |
1. 핵심 클래스: Pattern, Matcher
| Pattern |
정규 표현식(Regex)을 정의한 클래스 (Pattern.compile()) |
| Matcher |
입력 문자열에 대해 정규식을 검사, 매칭, 추출 등 수행 |
Pattern pattern = Pattern.compile("정규표현식");
Matcher matcher = pattern.matcher("입력문자열");
2. 주요 메서드
메서드
| find() |
매칭되는 부분이 있는지 확인 (있으면 true) |
| matches() |
문자열 전체가 정규식과 일치하는지 확인 |
| group() |
매칭된 문자열 반환 |
| replaceAll() |
매칭되는 부분을 다른 문자열로 치환 |
| split() |
정규식 기준으로 문자열 나누기 |
3. 자주 쓰는 정규 표현식 패턴
정규식 의미
| . |
모든 문자 한 개 |
| \d |
숫자 (0~9) |
| \D |
숫자가 아닌 문자 |
| \w |
단어 문자 (영문, 숫자, _) |
| \W |
단어 문자가 아닌 것 |
| \s |
공백 문자 |
| ^ |
문자열의 시작 |
| $ |
문자열의 끝 |
| `a |
b` |
| [] |
문자 집합 예: [abc]는 a, b, c 중 하나 |
| [^abc] |
a, b, c 제외한 문자 |
| ? |
0개 또는 1개 |
| * |
0개 이상 |
| + |
1개 이상 |
| {n} |
n개 반복 |
| {n,} |
n개 이상 |
| {n,m} |
n~m개 반복 |
4. 자주 쓰는 정규식 예시
용도 정규식
| 숫자만 |
^\d+$ |
| 영문 소문자만 |
^[a-z]+$ |
| 이메일 |
^[\w.-]+@[\w.-]+\.[a-zA-Z]{2,6}$ |
| 휴대폰 번호 |
^01[0-9]-\d{3,4}-\d{4}$ |
| 우편번호 (5자리) |
^\d{5}$ |
5. 예제 코드 ①: 이메일 유효성 검사
import java.util.regex.*;
public class RegexEmailExample {
public static void main(String[] args) {
String email = "test123@example.com";
String regex = "^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,6}$";
boolean isValid = Pattern.matches(regex, email);
System.out.println("이메일 유효?: " + isValid);
}
}
6. 예제 코드 ②: 텍스트에서 숫자 추출
import java.util.regex.*;
public class RegexNumberExtract {
public static void main(String[] args) {
String input = "총 합계는 1200원이며 할인은 300원입니다.";
Pattern pattern = Pattern.compile("\\d+");
Matcher matcher = pattern.matcher(input);
while (matcher.find()) {
System.out.println("숫자 추출: " + matcher.group());
}
}
}
7. 예제 코드 ③: 단어 치환
public class RegexReplace {
public static void main(String[] args) {
String str = "자바는 객체지향 언어입니다. 자바는 인기 많아요.";
String result = str.replaceAll("자바", "Java");
System.out.println(result);
}
}
8. 전체 요약
| Pattern |
정규식을 컴파일하는 클래스 |
| Matcher |
입력 문자열에 대해 검사/매칭/검색 수행 |
| matches() |
전체 일치 여부 |
| find() + group() |
부분 매칭 반복 검색 |
| replaceAll() |
정규식으로 치환 |
4단계. 예외 처리, 제네릭, 열거형, 람다식
| 예외 처리 |
try-catch, throws, finally, 사용자 정의 예외 |
| 제네릭 |
List<T>, 제네릭 메소드, 제한 (<T extends Number>) |
| 열거형 (Enum) |
상수 관리, 생성자 포함 enum |
| 람다식 & 함수형 인터페이스 |
Consumer, Supplier, Predicate, Function |
| 스트림 API |
filter, map, reduce, collect, parallelStream |
1. 예외란?
| 예외(Exception) |
프로그램 실행 중에 발생하는 비정상적인 상황 (파일 없음, 0으로 나누기 등) |
| 처리 목적 |
프로그램이 비정상 종료되지 않도록 예외를 잡아서 처리 |
2. 예외의 종류
| Checked Exception |
반드시 예외 처리를 해야 함 (IOException, SQLException 등) |
| Unchecked Exception |
컴파일러가 예외 처리 강제하지 않음 (NullPointerException, ArithmeticException 등) |
3. 기본 구조: try-catch
try {
// 예외가 발생할 수 있는 코드
} catch (예외타입 변수명) {
// 예외 발생 시 처리할 코드
}
try {
int x = 10 / 0; // ArithmeticException 발생
} catch (ArithmeticException e) {
System.out.println("0으로 나눌 수 없습니다.");
}
4. finally 블록
| 목적 |
예외 발생 여부와 관계없이 무조건 실행되는 블록 |
| 용도 |
파일 닫기, 자원 해제 등 마무리 작업에 사용 |
BufferedReader br = null;
try {
br = new BufferedReader(new FileReader("test.txt"));
System.out.println(br.readLine());
} catch (IOException e) {
System.out.println("파일 읽기 오류");
} finally {
if (br != null) try { br.close(); } catch (IOException e) {}
}
5. throws 키워드
| 설명 |
예외를 직접 처리하지 않고, 호출한 쪽에 예외를 넘김 |
| 대상 |
Checked Exception만 강제 처리 대상 |
public void readFile(String name) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(name));
System.out.println(br.readLine());
}
6. 사용자 정의 예외
| 설명 |
개발자가 필요에 따라 직접 만드는 예외 클래스 |
| 방법 |
Exception 또는 RuntimeException을 상속해서 정의 |
사용자 정의 예외 만들기
class MyException extends Exception {
public MyException(String msg) {
super(msg);
}
}
사용자 정의 예외 던지기
public void checkAge(int age) throws MyException {
if (age < 18) {
throw new MyException("18세 이상만 가능합니다.");
}
}
7. 전체 흐름 예제
public class ExceptionDemo {
public static void main(String[] args) {
try {
divide(10, 0);
} catch (ArithmeticException e) {
System.out.println("예외 발생: " + e.getMessage());
} finally {
System.out.println("항상 실행되는 finally 블록");
}
}
static void divide(int a, int b) {
System.out.println("결과: " + (a / b));
}
}
8. 예외 처리 요약
| try-catch |
예외 발생 가능 코드 실행 후, 예외를 잡아 처리 |
| finally |
예외 발생 여부와 관계없이 무조건 실행 |
| throws |
예외를 호출한 쪽으로 던짐 (위임) |
| throw |
예외를 명시적으로 발생시킴 |
| 사용자 정의 예외 |
개발자 정의 예외, Exception을 상속 |
9. 실무 팁
| 파일 입출력 |
IOException → 반드시 try-catch 또는 throws |
| 잘못된 사용자 입력 |
IllegalArgumentException, MyCustomException 등으로 구분 처리 |
| DB/네트워크 |
SQLException, SocketException 등 → 상세 로그 남기기 중요 |
| 리소스 정리 |
finally 또는 try-with-resources 사용 |
1. 제네릭(Generic)이란?
| 설명 |
클래스나 메소드 정의 시 데이터 타입을 매개변수처럼 사용하는 기능 |
| 목적 |
컴파일 타임 타입 안정성 확보, 형변환 최소화, 코드 재사용성 향상 |
| 문법 |
<T>, <K, V>, <T extends Number> 등 타입 매개변수 사용 |
2. 제네릭 List
| 설명 |
제네릭을 사용한 리스트로, 특정 타입만 저장할 수 있음 |
| 장점 |
타입 안정성 확보, Object → 형변환 불필요 |
List<String> list = new ArrayList<>();
list.add("Hello");
// list.add(123); // 컴파일 에러 발생!
String msg = list.get(0); // 형변환 없이 사용 가능
3. 제네릭 클래스
| 문법 |
class 클래스명<T> { ... } |
| 예 |
하나의 클래스가 여러 타입을 지원하도록 만듦 |
public class Box<T> {
private T item;
public void set(T item) { this.item = item; }
public T get() { return item; }
}
사용:
Box<String> b1 = new Box<>();
b1.set("Hi");
System.out.println(b1.get());
Box<Integer> b2 = new Box<>();
b2.set(100);
System.out.println(b2.get());
4. 제네릭 메소드
| 설명 |
메소드에 타입 매개변수를 선언하여 여러 타입 지원 |
| 문법 |
<T> 리턴타입 메소드명(T param) |
public class Util {
public static <T> void print(T item) {
System.out.println(item);
}
}
사용:
Util.print("Hello");
Util.<Integer>print(123); // 타입 명시도 가능
5. 제네릭 타입 제한 (<T extends Type>)
| 설명 |
특정 클래스나 인터페이스를 상속받은 타입만 허용 |
| 목적 |
T가 특정 기능(메서드 등)을 사용할 수 있게 제한 |
public class Calculator<T extends Number> {
public double square(T num) {
return num.doubleValue() * num.doubleValue();
}
}
사용:
Calculator<Integer> intCalc = new Calculator<>();
System.out.println(intCalc.square(5));
Calculator<Double> dblCalc = new Calculator<>();
System.out.println(dblCalc.square(2.5));
// Calculator<String> strCalc = new Calculator<>(); // 컴파일 에러!
6. 와일드카드 (<?>, <? extends T>, <? super T>)
와일드카드
| <?> |
모든 타입 허용 (읽기 전용) |
| <? extends T> |
T 또는 T의 하위 클래스만 허용 (읽기 전용) |
| <? super T> |
T 또는 T의 상위 클래스만 허용 (쓰기 가능) |
public void printAll(List<?> list) { ... }
public void sumAll(List<? extends Number> nums) {
double sum = 0;
for (Number n : nums) sum += n.doubleValue();
}
public void addAll(List<? super Integer> list) {
list.add(1); // 가능
}
7. 제네릭 요약
| List<T> |
T 타입의 리스트 |
| class Box<T> |
타입 매개변수 T를 사용하는 클래스 |
| <T> void print(T t) |
T 타입을 인자로 받는 제네릭 메소드 |
| <T extends Number> |
T는 Number 또는 그 하위 타입만 가능 |
| <?> |
알 수 없는 타입 (읽기 전용) |
| <? extends T> |
T의 하위 타입 |
| <? super T> |
T의 상위 타입 |
8. 실무 팁
| 특정 타입만 사용하게 하려면 |
<T extends 타입> |
| 다양한 타입 허용하되 읽기만 |
<?> |
| 리스트에 쓰기도 하려면 |
<? super T> |
| 자바 컬렉션 (List, Map 등)에서 유용 |
Map<String, List<Integer>> 형태 자주 사용 |
1. 열거형(enum) 기본 개념
| 설명 |
미리 정해진 고정된 상수 집합을 정의할 때 사용하는 특수한 클래스 |
| 특징 |
모든 상수는 객체처럼 취급되며, 타입 안정성 보장 |
| 선언 위치 |
클래스 외부, 내부 어디든 가능 (보통 별도 파일로 정의) |
public enum Direction {
NORTH, SOUTH, EAST, WEST
}
사용:
Direction d = Direction.NORTH;
System.out.println(d); // NORTH
System.out.println(d.name()); // "NORTH"
System.out.println(d.ordinal()); // 0
2. 열거형에 생성자와 필드 추가
| 설명 |
각 상수에 부가 정보를 추가하고 싶을 때 생성자와 필드 사용 |
| 특징 |
enum은 기본적으로 상수 객체이므로 생성자는 private만 가능 (생략 가능) |
public enum Grade {
BASIC("일반"), VIP("우수"), GOLD("최우수");
private final String label;
private Grade(String label) {
this.label = label;
}
public String getLabel() {
return label;
}
}
사용:
Grade g = Grade.VIP;
System.out.println(g.getLabel()); // "우수"
3. 열거형 메서드
| name() |
상수 이름 반환 (String) |
| ordinal() |
선언된 순서 (0부터 시작) |
| values() |
모든 enum 상수를 배열로 반환 |
| valueOf(String) |
문자열로 상수 조회 (없으면 예외 발생) |
for (Grade g : Grade.values()) {
System.out.println(g.name() + " = " + g.getLabel());
}
Grade gold = Grade.valueOf("GOLD");
4. 열거형에서 메서드 오버라이딩 (고급 활용)
| 설명 |
각 상수마다 다른 동작을 하게 하고 싶을 때 |
| 사용법 |
enum 내부에 추상 메서드를 정의하고 각 상수에서 오버라이딩 |
public enum Operation {
PLUS {
public int apply(int a, int b) { return a + b; }
},
MINUS {
public int apply(int a, int b) { return a - b; }
};
public abstract int apply(int a, int b);
}
사용:
System.out.println(Operation.PLUS.apply(3, 4)); // 7
5. 열거형과 switch문
| 설명 |
enum은 switch 문에서 사용할 수 있음 (가독성 우수) |
Direction dir = Direction.EAST;
switch (dir) {
case NORTH -> System.out.println("북쪽");
case SOUTH -> System.out.println("남쪽");
case EAST -> System.out.println("동쪽");
case WEST -> System.out.println("서쪽");
}
6. 열거형 활용 (실무)
상태 코드 관리
public enum StatusCode {
OK(200), NOT_FOUND(404), ERROR(500);
private final int code;
StatusCode(int code) {
this.code = code;
}
public int getCode() { return code; }
}
역할(Role) 분류
public enum Role {
ADMIN, USER, GUEST;
public boolean isAdmin() {
return this == ADMIN;
}
}
7. 열거형 요약
| enum 정의 |
고정된 상수 집합 정의 |
| 필드/생성자 추가 |
각 상수마다 추가 정보 저장 가능 |
| 메서드 정의 |
공통 또는 상수별 개별 동작 정의 가능 |
| values() |
모든 상수를 배열로 반환 |
| switch |
열거형을 스위치에서 사용 가능 |
1. 람다식(Lambda Expression)이란?
| 설명 |
익명 함수 표현식. 함수형 인터페이스를 간단히 구현 |
| 목적 |
코드 간결화, 전달 가능한 코드 블록, 함수형 프로그래밍 지원 |
| 전제 조건 |
**"추상 메서드가 1개인 인터페이스" (함수형 인터페이스)**만 사용 가능 |
기본 문법:
(매개변수) -> { 실행문 }
Runnable r = () -> System.out.println("Hello Lambda!");
2. 함수형 인터페이스
| 설명 |
추상 메서드가 1개인 인터페이스 (@FunctionalInterface로 표시 권장) |
| 특징 |
@FunctionalInterface 애너테이션을 붙이면 컴파일 타임 검증 가능 |
| 예시 |
Runnable, Comparator, java.util.function 패키지의 표준 인터페이스들 |
3. 주요 표준 함수형 인터페이스
1) Consumer
| 설명 |
입력은 받고 반환값은 없음 |
| 메서드 |
void accept(T t) |
| 예 |
로그 출력, 리스트 반복 처리 등 |
Consumer<String> printer = s -> System.out.println("출력: " + s);
printer.accept("Hello");
2) Supplier
| 설명 |
입력은 없고 반환값만 있음 |
| 메서드 |
T get() |
| 예 |
랜덤 값 제공, 객체 생성 |
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println("랜덤 값: " + randomSupplier.get());
3) Predicate
| 설명 |
입력값을 받아 true/false를 리턴 (조건 테스트용) |
| 메서드 |
boolean test(T t) |
| 예 |
필터링 조건, 유효성 검사 등 |
Predicate<String> isLong = s -> s.length() > 5;
System.out.println(isLong.test("Lambda")); // true
4) Function<T, R>
| 설명 |
입력 T를 받아 결과 R을 반환 |
| 메서드 |
R apply(T t) |
| 예 |
변환 함수, 매핑 등 |
Function<String, Integer> toLength = s -> s.length();
System.out.println(toLength.apply("Java")); // 4
4. 메서드 참조 (::)
| 설명 |
람다식을 간단히 표현하는 문법 |
| 형식 |
클래스명::메서드명 또는 참조변수::메서드명 |
List<String> names = List.of("Kim", "Lee", "Park");
names.forEach(System.out::println); // Consumer 람다식 간단화
5. 고급: 함수형 인터페이스 조합
메서드 | 대상 | 설명
| andThen() |
Consumer |
실행문 연결 |
| negate() |
Predicate |
조건 반전 |
| and(), or() |
Predicate |
조건 결합 |
| compose(), andThen() |
Function |
함수 합성 |
Predicate<String> isNotEmpty = s -> !s.isEmpty();
Predicate<String> isLong = s -> s.length() > 5;
Predicate<String> valid = isNotEmpty.and(isLong);
System.out.println(valid.test("Hello!")); // true
6. 실무 활용
리스트 필터링
List<String> list = List.of("apple", "banana", "kiwi");
list.stream()
.filter(s -> s.length() > 4)
.forEach(System.out::println);
매핑 처리
List<String> names = List.of("Kim", "Lee", "Park");
List<Integer> lengths = names.stream()
.map(String::length)
.toList();
7. 요약 비교표
인터페이스 | 입력 | 반환 | 메서드 | 예시
| Consumer |
T |
void |
accept(T t) |
로그 출력 |
| Supplier |
없음 |
T |
get() |
랜덤값 생성 |
| Predicate |
T |
boolean |
test(T t) |
조건 체크 |
| Function<T, R> |
T |
R |
apply(T t) |
변환 함수 |
1. Stream API란?
| 설명 |
컬렉션(또는 배열)의 요소를 함수형 방식으로 처리하는 API |
| 특징 |
내부 반복, 지연 평가(Lazy), 메서드 체이닝, 병렬 처리 가능 |
| 구성 |
중간 연산(filter, map 등) + 최종 연산(forEach, reduce 등) |
list.stream().filter(...).map(...).collect(...);
2. filter()
| 설명 |
조건에 맞는 요소만 추출 (boolean 반환하는 Predicate 사용) |
| 리턴 |
Stream (원본 스트림에서 일부 요소만 필터링) |
List<String> names = List.of("Kim", "Lee", "Kang", "Park");
names.stream()
.filter(n -> n.startsWith("K"))
.forEach(System.out::println); // Kim, Kang
3. map()
| 설명 |
스트림 요소를 변환(mapping) (입력 → 출력) |
| 리턴 |
Stream (타입 변경 가능) |
List<String> names = List.of("Java", "Python", "Go");
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList()); // [4, 6, 2]
4. reduce()
| 설명 |
모든 요소를 하나의 결과로 누적 |
| 리턴 |
Optional 또는 T |
| 사용 형태 |
(초기값, (a, b) -> 누적) 또는 (BinaryOperator) |
int sum = List.of(1, 2, 3, 4, 5)
.stream()
.reduce(0, (a, b) -> a + b); // 15
또는 람다 간략화:
.reduce(0, Integer::sum)
5. collect()
| 설명 |
스트림을 리스트/맵/문자열 등으로 수집 |
| 리턴 |
List, Map, String 등 |
| 주요 클래스 |
Collectors (유틸 클래스) |
List<String> list = List.of("a", "bb", "ccc");
List<Integer> lengths = list.stream()
.map(String::length)
.collect(Collectors.toList()); // [1, 2, 3]
다양한 Collectors
| toList() |
리스트로 수집 |
| toSet() |
중복 제거 후 집합으로 수집 |
| joining() |
문자열 합치기 |
| groupingBy() |
그룹화 |
| partitioningBy() |
true/false로 분리 |
6. parallelStream()
| 설명 |
멀티코어 CPU에서 병렬 처리 (성능 향상 가능) |
| 사용법 |
list.parallelStream() |
| 특징 |
순서 보장 안 됨, 부작용(side effect) 함수는 피해야 함 |
List<Integer> nums = IntStream.rangeClosed(1, 1000000)
.boxed()
.collect(Collectors.toList());
long sum = nums.parallelStream()
.mapToLong(n -> n)
.sum();
System.out.println("합계: " + sum);
7. 전체 예제
List<String> words = List.of("Java", "Stream", "Filter", "Map");
int totalLength = words.stream()
.filter(w -> w.length() > 4)
.map(String::length)
.reduce(0, Integer::sum);
System.out.println("총 길이: " + totalLength); // Filter(6), Stream(6) → 12
8. 요약 비교표
메서드 | 유형 | 설명 | 리턴값
| filter(Predicate) |
중간 |
조건에 맞는 요소만 추출 |
Stream |
| map(Function) |
중간 |
요소 변환 |
Stream |
| reduce() |
최종 |
누적 처리 |
T 또는 Optional |
| collect(Collectors) |
최종 |
결과 수집 |
List, Set, String 등 |
| parallelStream() |
시작 |
병렬 스트림 시작 |
병렬 Stream |
9. 실무 팁
| 조건 필터링 |
filter() |
| 값 변환 |
map() |
| 총합/누적 |
reduce() |
| 결과 수집 |
collect(Collectors.toList()) |
| 성능 최적화 |
parallelStream() + map/reduce only |
5단계. 스레드와 동시성 프로그래밍
| Thread & Runnable |
스레드 생성 및 실행 |
| 동기화 (synchronized) |
공유 자원 접근 제어 |
| wait/notify |
동기화 객체 간 통신 |
| Executors |
스레드 풀, Future, Callable |
| java.util.concurrent 패키지 |
ConcurrentHashMap, CountDownLatch, Semaphore |
1. 스레드란?
| 정의 |
하나의 프로세스 내부에서 동시에 실행 가능한 작업 단위 (작은 실행 흐름) |
| 특징 |
메모리 공간 공유, 동시성(Concurrency) 수행 가능 |
| 생성 방식 |
Thread 클래스 상속 또는 Runnable 인터페이스 구현 |
2. Thread 클래스 상속 방식
| 특징 |
Thread를 직접 상속하여 run() 메서드를 오버라이드 |
| 사용법 |
start() 메서드로 실행 (run()이 아니라!) |
public class MyThread extends Thread {
public void run() {
System.out.println("Thread 실행: " + getName());
}
public static void main(String[] args) {
MyThread t1 = new MyThread();
t1.start(); // run()이 아닌 start() 호출
}
}
3. Runnable 인터페이스 구현 방식
| 특징 |
상속 제약이 없고, 실행 로직을 별도로 분리 가능 |
| 문법 |
Runnable을 구현한 객체를 Thread에 넘겨줌 |
public class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable 실행: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
4. 익명 클래스 또는 람다식 (자주 씀!)
익명 클래스
Thread t = new Thread(new Runnable() {
public void run() {
System.out.println("익명 Runnable 실행");
}
});
t.start();
람다식 (Java 8+)
Thread t = new Thread(() -> {
System.out.println("람다 Runnable 실행");
});
t.start();
5. Thread vs Runnable 비교표
항목 | Thread 상속 | Runnable 구현
| 클래스 제약 |
다른 클래스 상속 불가 |
다른 클래스와 동시 상속 가능 |
| 구조 |
실행 코드와 Thread 자체가 하나 |
실행 로직을 외부에서 전달 |
| 재사용성 |
낮음 |
높음 |
| 권장도 |
단순 테스트용 |
실무에서는 Runnable 추천 |
6. 여러 스레드 실행
public class MultiThreadExample {
public static void main(String[] args) {
for (int i = 1; i <= 3; i++) {
int id = i;
Thread t = new Thread(() -> {
System.out.println("스레드 " + id + " 실행 중");
});
t.start();
}
}
}
7. start() vs run()
| start() |
새로운 스레드 생성 후 run() 실행 (병렬 실행) |
| run() |
단순 메서드 호출이므로 스레드 생성 안 됨 (동기 실행) |
8. 실무 팁
| 여러 작업을 병렬 처리하고 싶을 때 |
Runnable, ExecutorService |
| 클래스 상속을 이미 쓰고 있다면 |
Runnable로 분리 |
| UI 이벤트 처리 또는 서버 요청 처리 |
스레드 필수 |
| 복잡한 스레드 관리 |
ThreadPoolExecutor, Future, Callable 활용 |
1. 동기화(Synchronization)란?
| 정의 |
여러 스레드가 하나의 공유 자원에 접근할 때 동시성 문제를 방지하는 기법 |
| 목적 |
데이터 무결성 유지, 스레드 간 간섭(interference) 방지 |
| 원리 |
한 시점에 하나의 스레드만 특정 블록에 접근하도록 **락(lock)**을 걸음 |
2. 왜 필요한가? (문제 상황)
여러 스레드가 동시에 아래 메서드를 호출한다고 가정해보자:
public void increase() {
count++; // 비원자적 연산: 읽고, 증가하고, 저장
}
문제
| 경쟁 조건(Race Condition) |
여러 스레드가 동시에 count 값을 읽고, 동시에 수정하여 예상치 못한 결과 발생 |
| 결과 |
count 값이 덜 증가함 (데이터 손실) |
3. synchronized 키워드 사용법
1) 메서드 전체를 동기화
public synchronized void increase() {
count++;
}
| 특징 | 이 메서드를 호출하는 모든 스레드는 순차적 실행됨 |
2) 특정 블록만 동기화 (권장)
public void increase() {
synchronized (this) {
count++;
}
}
| 특징 | 꼭 필요한 코드 부분만 동기화 → 성능 향상 가능 |
4. static 메서드 동기화
public static synchronized void increaseStatic() {
staticCount++;
}
또는
synchronized (클래스이름.class) {
staticCount++;
}
| 목적 |
클래스 단위로 lock을 걸기 위함 |
| 대상 |
static 변수 등 클래스 공유 자원 |
5. 경쟁 조건 vs 동기화
경쟁 발생
public class Counter {
int count = 0;
public void increase() {
count++;
}
}
Counter c = new Counter();
Runnable task = () -> {
for (int i = 0; i < 10000; i++) {
c.increase();
}
};
Thread t1 = new Thread(task);
Thread t2 = new Thread(task);
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println(c.count); // 결과가 20000이 아닐 수 있음!
동기화 적용 후
public class Counter {
int count = 0;
public synchronized void increase() {
count++;
}
}
또는
public void increase() {
synchronized (this) {
count++;
}
}
결과:
System.out.println(c.count); // 항상 20000
6. 주의할 점
| 과도한 동기화 |
모든 코드에 락을 걸면 병목 발생 가능 |
| 데드락(Deadlock) |
서로가 서로의 락을 기다리는 상황 → 락 순서 통일 필요 |
| volatile vs synchronized |
volatile은 가시성 보장만, synchronized는 원자성 보장도 포함 |
7. 요약 비교표
키워드 | 대상 | 락 대상 | 특징
| synchronized 인스턴스 메서드 |
인스턴스 |
this |
객체마다 락 별도 |
| synchronized 정적 메서드 |
클래스 |
클래스이름.class |
클래스 전체 공유 락 |
| synchronized(this) |
특정 블록 |
this |
더 정밀한 제어 가능 |
1. wait/notify란?
| 목적 |
스레드 간 통신: 공유 객체를 기준으로 일시정지 및 재시작 |
| 사용 조건 |
반드시 synchronized 블록 안에서만 사용 가능 |
| 적용 대상 |
모든 Object (자바의 모든 객체는 monitor 락을 가짐) |
2. 메서드 개요
| wait() |
현재 스레드를 일시 정지 (다른 스레드가 notify()할 때까지 대기) |
| notify() |
기다리는 스레드 중 하나를 깨움 |
| notifyAll() |
모든 기다리는 스레드를 깨움 |
3. 동작 구조 (간단 흐름)
- Thread A가 공유 객체의 synchronized 블록에 진입 → wait() 호출 → 대기 상태
- Thread B가 같은 공유 객체의 synchronized 블록에 진입 → 작업 수행 후 notify() 호출
- Thread A가 다시 깨어나서 실행 재개
4. 예제: 생산자-소비자 패턴 (공유 큐 사용)
class SharedQueue {
private int item;
private boolean available = false;
public synchronized void produce(int value) {
while (available) {
try { wait(); } catch (InterruptedException e) {}
}
item = value;
System.out.println("생산: " + value);
available = true;
notify();
}
public synchronized int consume() {
while (!available) {
try { wait(); } catch (InterruptedException e) {}
}
System.out.println("소비: " + item);
available = false;
notify();
return item;
}
}
사용:
SharedQueue queue = new SharedQueue();
Thread producer = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
queue.produce(i);
}
});
Thread consumer = new Thread(() -> {
for (int i = 1; i <= 5; i++) {
queue.consume();
}
});
producer.start();
consumer.start();
5. 주의 사항
| wait()은 InterruptedException 발생 가능 |
반드시 try-catch 필요 |
| synchronized 없이 wait() 사용하면 IllegalMonitorStateException |
|
| notify()만 쓰면 스레드가 영원히 못 깨어날 수 있음 → notifyAll()이 안전할 수 있음 |
|
| while로 조건 검사하는 이유 |
spurious wakeup (의도치 않게 깨어나는 현상) 방지 |
6. 요약 비교표
메서드 | 효과 | 사용 조건
| wait() |
현재 스레드 일시 정지 (다른 스레드가 깨울 때까지) |
synchronized 블록 내부 |
| notify() |
대기 중인 스레드 중 1개를 깨움 |
synchronized 블록 내부 |
| notifyAll() |
대기 중인 스레드 전체를 깨움 |
synchronized 블록 내부 |
7. 실무 팁
| 생산자-소비자 패턴 |
wait/notify 대표 사용 예 |
| 스레드 간 상태 변경 기다릴 때 |
wait() 활용 |
| 복잡한 락 관리 필요 |
java.util.concurrent 패키지 (BlockingQueue, Condition, ReentrantLock)로 대체 권장 |
1. Executors & 스레드 풀(Thread Pool)
| 목적 |
스레드를 직접 생성/관리하지 않고 풀(Pool)로 관리 |
| 장점 |
스레드 재사용, 리소스 낭비 방지, 안정성 향상 |
| 제공 클래스 |
Executors (유틸리티), ExecutorService (실행 서비스) |
스레드 풀 생성 방법
ExecutorService executor = Executors.newFixedThreadPool(3);
| newFixedThreadPool(n) |
고정된 개수의 스레드 풀 |
| newCachedThreadPool() |
필요할 때마다 새 스레드 생성, 유휴 시 제거 |
| newSingleThreadExecutor() |
하나의 스레드로 순차 처리 |
2. Runnable vs Callable
구분 | Runnable | Callable
| 반환값 |
없음 (void) |
있음 (V) |
| 예외 처리 |
throws 불가 |
throws Exception 가능 |
| 스레드 제출 |
execute() |
submit() → Future 리턴 |
3. Callable & Future
Callable 정의
Callable<Integer> task = () -> {
Thread.sleep(1000);
return 10 + 20;
};
Callable 제출 및 결과 받기
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(task);
System.out.println("계산 중...");
int result = future.get(); // 블로킹: 작업 완료까지 대기
System.out.println("결과: " + result);
executor.shutdown();
4. Future 주요 메서드
| get() |
결과를 반환 (작업 완료까지 대기) |
| get(timeout, TimeUnit) |
지정 시간만 대기, 초과 시 TimeoutException |
| isDone() |
작업 완료 여부 확인 |
| cancel(true) |
작업 취소 시도 (중단 요청) |
| isCancelled() |
작업이 취소되었는지 확인 |
5. 여러 작업을 동시에 제출하고 결과 받기
ExecutorService executor = Executors.newFixedThreadPool(3);
List<Callable<String>> tasks = List.of(
() -> "🍎",
() -> "🍊",
() -> "🍌"
);
List<Future<String>> results = executor.invokeAll(tasks);
for (Future<String> f : results) {
System.out.println("결과: " + f.get());
}
executor.shutdown();
6. 요약 비교표
클래스/인터페이스 | 역할 | 특징
| Executors |
스레드 풀 생성 도구 |
유틸리티 클래스 |
| ExecutorService |
스레드 풀 인터페이스 |
submit(), shutdown() 등 제공 |
| Runnable |
실행만 가능 |
반환값/예외 없음 |
| Callable<V> |
반환값 있는 작업 정의 |
예외 처리 가능 |
| Future<V> |
비동기 결과를 나중에 받는 객체 |
get(), cancel() 등 사용 |
7. 실무 팁
| 병렬 작업 여러 개 실행 & 결과 기다림 |
invokeAll(List<Callable>) |
| 결과 필요 없는 단순 작업 |
Runnable + execute() |
| 시간이 오래 걸릴 수 있는 작업 |
Callable + Future |
| 서버 대기열 처리 |
newFixedThreadPool(n) 사용 |
1. ConcurrentHashMap
| 설명 |
멀티스레드 환경에서 안전하게 사용할 수 있는 Map 구현체 |
| 특징 |
내부적으로 세그먼트(버킷)를 나눠서 부분 락 → 동시 쓰기 가능 |
| 차이점 |
HashMap: 스레드 안전하지 않음, Hashtable: 전체 락이라 성능 저하 |
| 메서드 |
putIfAbsent(), compute(), forEach(), merge() 등 병렬 최적화된 메서드 제공 |
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("apple", 1);
map.putIfAbsent("apple", 2); // 기존 값이 있으면 덮어쓰지 않음
map.compute("apple", (k, v) -> v + 1); // apple: 2
System.out.println(map.get("apple"));
2. CountDownLatch
| 설명 |
스레드들이 완료될 때까지 대기하기 위한 유틸 |
| 역할 |
N개의 작업이 완료될 때까지 기다림 (단방향 동기화) |
| 생성자 |
new CountDownLatch(int count) |
| 주요 메서드 |
countDown(), await() |
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println("작업 수행...");
latch.countDown(); // 하나 완료
}).start();
}
latch.await(); // 메인 스레드는 모든 작업이 끝날 때까지 대기
System.out.println("모든 작업 완료");
3. Semaphore
| 설명 |
허용된 수량만큼의 자원을 공유할 수 있게 제한 |
| 역할 |
동시에 접근할 수 있는 스레드 수를 제어 (트래픽 제한, 연결 수 제한 등) |
| 생성자 |
new Semaphore(int permits) |
| 주요 메서드 |
acquire(), release() |
Semaphore semaphore = new Semaphore(2); // 동시에 2개만 접근 가능
for (int i = 1; i <= 5; i++) {
int id = i;
new Thread(() -> {
try {
semaphore.acquire();
System.out.println("Thread-" + id + " 실행 중");
Thread.sleep(1000);
} catch (InterruptedException e) {}
finally {
System.out.println("Thread-" + id + " 종료");
semaphore.release();
}
}).start();
}
4. 비교 요약표
클래스 | 목적 | 주 사용 시기 | 동작 방식
| ConcurrentHashMap |
스레드 안전한 Map |
여러 스레드가 동시에 Map을 읽고/쓰기 |
세분화된 락으로 높은 동시성 |
| CountDownLatch |
특정 수만큼 완료되기까지 대기 |
여러 작업 후 후속 작업 실행 |
카운트 0이 되면 unblock |
| Semaphore |
동시에 실행될 수 있는 스레드 수 제한 |
DB 커넥션 풀, 요청 제한 |
acquire/release로 허가 조절 |
5. 실무 팁
| 멀티스레드에서 데이터 누락 없이 맵 처리 |
ConcurrentHashMap |
| 여러 백그라운드 작업이 끝난 후 작업 실행 |
CountDownLatch |
| 동시에 접속할 수 있는 자원 제한 |
Semaphore |
6단계. JVM과 Java 실행 원리
| JVM 구조 |
클래스 로더, 메서드 영역, 힙, 스택 |
| GC (Garbage Collection) |
메모리 관리 전략 |
| 바이트코드 이해 |
javap, 디컴파일 |
| ClassLoader 동작 원리 |
Bootstrap, Extension, Application |
1. JVM 전체 구조 개요
| 정의 |
자바 바이트코드를 실행하기 위한 가상 머신 |
| 목적 |
플랫폼 독립성, 메모리 관리, 실행 최적화, 보안 |
| 큰 구성 |
클래스 로더 + 실행 엔진 + 메모리 영역 + 네이티브 인터페이스 |
2. 클래스 로더(Class Loader)
| 역할 |
.class 파일을 읽어서 메서드 영역에 클래스 정보 로드 |
| 구성 |
Bootstrap → Extension → Application 3단계 로더 계층 구조 |
| 특징 |
로드(Load) → 검증(Verify) → 준비(Prepare) → 해석(Resolve) → 초기화(Init) 순으로 동작 |
| 예시 |
String.class, 사용자 정의 클래스 등 모두 JVM 시작 시 로드됨 |
3. 메서드(Method) 영역
| 역할 |
클래스의 메타 정보 저장 (클래스명, 필드, 메서드 시그니처 등) |
| 포함 내용 |
static 변수, final 상수, 메서드 바이트코드 등 |
| 특징 |
모든 스레드가 공유하는 영역 |
| 위치 |
Java 8까지는 PermGen, Java 8 이후부터는 Metaspace (native 영역에 위치) |
4. 힙(Heap)
| 역할 |
객체 인스턴스와 그들의 필드 저장 |
| 특징 |
모든 스레드가 공유 / 가장 크고 GC(Garbage Collector)의 주요 대상 |
| 생성 시점 |
JVM 시작 시 생성 |
| 예시 |
new로 생성한 객체, 배열 등 모두 힙에 저장됨 |
5. 스택(Stack)
| 역할 |
메서드 호출 시의 실행 정보 저장 (지역 변수, 매개변수, 반환 주소 등) |
| 구성 |
메서드마다 하나의 스택 프레임(Stack Frame) 생성 |
| 특징 |
각 스레드마다 독립된 공간 / 메서드 호출 → 스택 쌓이고 → 종료되면 팝됨 |
| 저장되는 것 |
기본형 변수, 참조형 변수 주소 등 |
int sum(int a, int b) {
int result = a + b; // result는 스택에 저장됨
return result;
}
6. PC 레지스터 (Program Counter Register)
| 역할 |
현재 실행 중인 명령어 주소를 기록 |
| 특징 |
스레드마다 별도로 존재 (즉, 멀티스레드 지원) |
| 예시 |
JVM이 어떤 바이트코드를 실행할지 추적하는 데 사용됨 |
7. 네이티브 메서드 스택 (Native Method Stack)
| 역할 |
C/C++로 작성된 네이티브 메서드 호출을 위한 메모리 |
| 특징 |
JNI(Java Native Interface)를 통해 C 코드 호출 시 사용 |
| 예시 |
System.arraycopy(), Thread.sleep() 같은 내부 C 함수 호출 때 |
8. JVM 메모리 구조 요약표
영역 | 설명 | 스레드 공유 여부
| 클래스 로더 |
.class 파일을 JVM에 로드 |
전체 공유 |
| 메서드 영역 |
클래스의 메타데이터, static, 상수 |
전체 공유 |
| 힙 영역 |
객체 인스턴스 저장 |
전체 공유 |
| 스택 영역 |
메서드 호출 시 지역 변수 저장 |
스레드별 생성 |
| PC 레지스터 |
현재 실행 중인 명령어 주소 |
스레드별 생성 |
| 네이티브 메서드 스택 |
JNI 호출 시 사용되는 C 스택 |
스레드별 생성 |
9. 실행 흐름 요약
- Java 코드 컴파일 → .class 파일 생성
- 클래스 로더가 JVM에 클래스 로드
- 메서드 영역에 클래스 정보 저장
- main 메서드 실행 → 스택 프레임 생성
- new → 객체는 힙에 저장, 참조값은 스택에
- 명령어 흐름은 PC 레지스터가 추적
- 네이티브 메서드 호출 시 네이티브 메서드 스택 사용
- 객체가 사용되지 않으면 → GC(Garbage Collector)가 힙에서 정리
10. 추가 학습 추천
| GC (가비지 컬렉션) 원리 |
힙 관리를 위한 핵심 메커니즘 |
| JIT 컴파일, HotSpot |
성능 최적화 방식 이해 |
| JVM 튜닝 옵션 |
-Xmx, -Xms, -XX:+PrintGC 등 |
| 클래스 로더 동작 원리 |
로딩 순서, 커스텀 로더 구현 시 필수 |
1. GC란?
| 정의 |
Java 힙 메모리에서 더 이상 참조되지 않는 객체를 자동으로 제거하는 기능 |
| 목적 |
메모리 누수 방지, 개발자가 메모리 해제하지 않아도 됨 |
| 특징 |
JVM이 자동 수행, 힙 영역의 생명주기를 관리함 |
2. JVM 힙 구조 (GC 관점)
[ Young Generation ]
├─ Eden
└─ Survivor (S0, S1)
[ Old Generation ]
[ (Java 8까지) PermGen → Java 8 이후 Metaspace ]
| Young |
새로 생성된 객체가 저장되는 영역 (많이 생성/삭제됨) |
| Eden |
대부분의 객체가 처음 할당되는 공간 |
| Survivor |
GC 후 살아남은 객체가 이동 |
| Old |
오래 살아남은 객체가 저장되는 공간 (Full GC의 대상) |
| Metaspace |
클래스 정보 저장 (Java 8 이상) |
3. GC의 기본 원리
| Reachability (도달 가능성) |
어떤 객체가 **루트(root)**에서 참조되고 있는가? |
| GC Root |
참조의 시작점 (스택, static, JNI 등) |
| Mark-and-Sweep |
사용 중인 객체를 마크하고, 안 쓰는 객체는 정리 |
| Copying |
살아 있는 객체만 복사 (Survivor → Survivor or Old) |
| Compacting |
파편화된 메모리 재배치 (Old GC 후) |
4. Minor GC vs Major GC (Full GC)
종류 | 대상 영역 | 트리거 | 특징
| Minor GC |
Young 영역 (Eden + Survivor) |
Eden 가득 찼을 때 |
빠름, 자주 발생 |
| Major GC / Full GC |
Old 영역 + Metaspace |
Old 가득 찼을 때 |
느림, 애플리케이션 멈춤 가능 |
5. 대표 GC 알고리즘
이름 | 특징 | 사용 환경
| Serial GC |
단일 스레드로 GC 수행 |
메모리 적고 단순한 앱 |
| Parallel GC |
멀티스레드로 GC 수행 (Default) |
Throughput 중요 |
| CMS (Concurrent Mark-Sweep) |
앱 동작과 GC 동시 진행 (부분 병렬) |
Stop-the-world 최소화 |
| G1 GC |
Region 단위 관리 + 예측 가능한 GC 시간 |
Java 9+ 기본, 서버 적합 |
| ZGC / Shenandoah |
거의 멈추지 않는 GC |
Java 11+ 이상, 실시간 시스템용 |
6. GC 동작 예시 흐름
- new로 객체 생성 → Eden 영역에 저장
- Eden이 가득 차면 → Minor GC 발생
- 살아남은 객체는 → Survivor로 이동
- 여러 번 Minor GC 후에도 살아남으면 → Old로 승격
- Old도 가득 차면 → Full GC (Major GC) 발생
7. GC 튜닝 JVM 옵션 (기초)
| -Xms |
초기 힙 크기 |
| -Xmx |
최대 힙 크기 |
| -Xss |
스택 크기 |
| -XX:+UseG1GC |
G1 GC 사용 |
| -XX:+PrintGCDetails |
GC 상세 로그 출력 |
| -XX:MaxMetaspaceSize |
Metaspace 최대 크기 (Java 8+) |
8. 실무 팁
| 빈번한 GC로 성능 저하 |
힙 크기 늘리기, G1 GC로 전환 고려 |
| Latency(지연) 민감한 앱 |
CMS, G1 또는 ZGC 사용 고려 |
| 메모리 누수 의심 |
GC 로그 분석, VisualVM/Memory Analyzer 도구 사용 |
9. 용어 요약
| GC Root |
참조 그래프의 시작점 (스택, static 등) |
| Stop-the-world |
GC 수행 중 애플리케이션 전체 일시 중지 |
| Survivor |
Young 영역 중 살아남은 객체가 이동하는 공간 |
| Promotion |
Young → Old 로 객체가 이동되는 것 |
| Compaction |
Old 영역 파편화 해소 (GC 후 압축 정리) |
10. 시각적으로 요약
[스택] → 참조 → [Eden] → [Survivor] → [Old]
↑ GC Minor GC Full GC
GC Root
1. 바이트코드란?
| 정의 |
자바 소스 코드(.java)를 *컴파일러(javac)*가 JVM이 이해할 수 있는 *중간 코드(.class)*로 변환한 것 |
| 특징 |
운영체제에 독립적이며, JVM에서 실행됨 |
| 목적 |
플랫폼 독립성과 JVM 실행 최적화를 위한 중간 언어 |
예시 흐름
// Hello.java
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}
javac Hello.java ← 컴파일
java Hello ← 실행 (JVM이 Hello.class 읽음)
2. .class 파일의 구조 (간단히)
| Magic Number |
클래스 파일 여부 식별 (0xCAFEBABE) |
| Version Info |
Java 버전 |
| Constant Pool |
상수 테이블 (문자열, 숫자, 클래스 이름 등) |
| Class Info |
접근자, 상속 정보 |
| Method Info |
메서드 이름, 바이트코드, 스택 등 |
| Attribute Info |
디버깅 정보, 주석 등 부가 정보 |
3. javap: 바이트코드 분석 도구
| 역할 |
.class 파일을 사람이 읽을 수 있는 형태로 디코딩해주는 JDK 제공 도구 |
| 실행 예 |
javap -c Hello (컴파일된 Hello.class 분석) |
| 옵션 |
-c (바이트코드 출력), -p (private 포함 모든 멤버 출력), -v (상세 정보) 등 |
사용
javac Hello.java
javap -c Hello
출력
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #3 // String Hello, Java!
5: invokevirtual #4 // Method println
8: return
명령어
| getstatic |
static 필드 읽기 (System.out) |
| ldc |
상수 로드 ("Hello, Java!") |
| invokevirtual |
메서드 호출 (println) |
| return |
반환 |
4. 디컴파일 (Decompile)
| 정의 |
.class 바이트코드를 다시 Java 소스 코드로 변환하는 작업 |
| 용도 |
소스 코드 없이 .class만 있을 때 코드 확인, 분석, 복구 용도 |
| 도구 |
JD-GUI, FernFlower, CFR, JADX 등 |
JD-GUI 사용 예
- .class 또는 .jar 파일 열기
- GUI에서 클래스 구조와 메서드 내용을 Java 코드 형태로 확인 가능
- "Save All Sources"로 .java 파일로 추출 가능
5. 바이트코드와 디컴파일 요약표
| .class 파일 |
JVM이 실행하는 중간 언어 |
| javap |
.class → 바이트코드 명령어 수준 확인 |
| 디컴파일러 |
.class → Java 코드 형태로 복원 |
| 사용 목적 |
코드 최적화, 보안 분석, 이해, 리버스 엔지니어링 |
6. 바이트코드 보는 이유
| 성능 분석 |
어떤 코드가 더 가벼운 바이트코드를 만드는지 분석 |
| 보안 검토 |
소스 없이 로직 확인 |
| 코드 최적화 |
불필요한 객체 생성, 박싱 등 감지 가능 |
| JVM 동작 이해 |
JIT 컴파일, 인터프리터 방식의 근거 확인 가능 |
7. 실전 팁
| CLI에서 바이트코드 확인 |
javap -c -p 클래스명 |
| 전체 클래스 구조 확인 |
javap -verbose 클래스명 |
| GUI로 .class 분석 |
JD-GUI, IntelliJ 내장 디컴파일러 |
| 바이트코드 변경 실험 |
ASM, ByteBuddy, Javassist 같은 바이트코드 조작 라이브러리 활용 |
1. ClassLoader란?
| 정의 |
.class 파일을 **JVM 메서드 영역(Method Area)**에 로딩하는 역할을 하는 객체 |
| 역할 |
클래스를 동적으로 메모리에 로드(Load) → 검증(Verify) → 준비(Prepare) → 해석(Resolve) → 초기화(Initialize) |
| 특징 |
자바의 모든 클래스는 반드시 클래스 로더를 통해 로드되어야 함 |
2. ClassLoader 계층 구조
[ Bootstrap ClassLoader ]
↑
[ Extension ClassLoader ]
↑
[ Application ClassLoader ]
각 로더는 자기 자신이 로드할 수 없으면 부모에게 위임하는 구조를 가짐
이를 Parent Delegation Model (부모 위임 모델)이라고 함
3. ClassLoader 종류
로더 | 설명 | 클래스 위치
| Bootstrap ClassLoader |
JVM의 핵심 클래스 로더, C/C++로 구현되어 있음 |
rt.jar, java.base, java.lang.*, java.util.* |
| Extension ClassLoader |
확장 기능을 위한 클래스 로딩 담당 |
$JAVA_HOME/lib/ext 또는 jre/lib/ext |
| Application ClassLoader |
우리가 작성한 일반 클래스들 로딩 |
classpath (., bin/, target/classes/, lib/*.jar) |
4. Parent Delegation Model (부모 위임 모델)
| 1단계 |
Application ClassLoader가 클래스 로딩 요청을 받음 |
| 2단계 |
먼저 부모인 Extension ClassLoader에 위임 |
| 3단계 |
Extension도 모르면 Bootstrap에 위임 |
| 4단계 |
Bootstrap이 로딩 시도, 실패하면 아래로 내려옴 |
| 5단계 |
Application ClassLoader가 최종적으로 클래스 로드 시도 |
이 방식은 클래스 중복 로딩을 방지하고,
JDK의 핵심 클래스가 사용자 클래스에 의해 재정의되는 것을 막기 위해 존재함.
5. 실전 확인 방법
public class ClassLoaderCheck {
public static void main(String[] args) {
System.out.println("String: " + String.class.getClassLoader()); // null → Bootstrap
System.out.println("ClassLoaderCheck: " + ClassLoaderCheck.class.getClassLoader()); // AppClassLoader
System.out.println("ClassLoader of AppClassLoader: " + ClassLoaderCheck.class.getClassLoader().getParent()); // ExtClassLoader
}
}
출력:
String: null
ClassLoaderCheck: jdk.internal.loader.ClassLoaders$AppClassLoader@...
ClassLoader of AppClassLoader: jdk.internal.loader.ClassLoaders$PlatformClassLoader@...
6. 사용자 정의 ClassLoader
| 필요할 때 |
암호화된 클래스, 네트워크에서 클래스 로딩 등 |
| 구현 방법 |
extends ClassLoader → findClass() 오버라이딩 |
| 주의 사항 |
parent delegation 구조를 변경하면 보안에 주의 필요 |
7. ClassLoader 요약표
구분 | 설명 | 위치
| Bootstrap |
JVM 핵심 클래스 로더 (native) |
Java 기본 라이브러리 (rt.jar, java.base) |
| Extension |
확장 클래스 로더 |
$JAVA_HOME/lib/ext |
| Application |
사용자 클래스 로더 |
클래스패스에서 .class/.jar 로딩 |
| Custom |
사용자 정의 |
직접 구현 (보안, 역직렬화, 암호화 등) |
8. 정리 이미지 (텍스트 구조)
[ Bootstrap ]
↑
[ Extension ]
↑
[ Application ] ← 우리가 사용하는 클래스 로더
9. 실무 팁
| 클래스를 못 찾는 경우 (ClassNotFoundException) |
classpath 설정 확인, 클래스 로더 위임 확인 |
| 동일한 이름의 클래스가 다른 로더에서 중복 로딩됨 |
ClassCastException 또는 NoClassDefFoundError 발생 가능 |
| 고급 프레임워크에서 ClassLoader 직접 사용 |
Spring, Tomcat, OSGi 등은 별도의 로딩 전략 사용 |
7단계. 빌드 & 의존성 관리 도구
| Maven |
pom.xml, 의존성 관리, 라이프사이클 |
| Gradle |
build.gradle, 태스크 시스템 |
| 모듈 시스템 |
module-info.java, requires, exports, 모듈 경로 |
1. Maven이란?
| 정의 |
Java 기반 프로젝트의 자동화 빌드 도구 |
| 기능 |
의존성 관리, 빌드 관리, 프로젝트 구조 통일 |
| 설정 파일 |
pom.xml (Project Object Model) |
2. pom.xml의 주요 구성 요소
<project>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId> <!-- 그룹명 -->
<artifactId>my-app</artifactId> <!-- 프로젝트 이름 -->
<version>1.0.0</version> <!-- 버전 -->
<dependencies> <!-- 의존성 관리 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.30</version>
</dependency>
</dependencies>
<build> <!-- 빌드 설정 -->
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.10.1</version>
<configuration>
<source>17</source>
<target>17</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3. Maven 표준 디렉토리 구조
my-app/
├── pom.xml
└── src/
├── main/
│ ├── java/ ← 소스 코드
│ └── resources/ ← 설정 파일
└── test/
├── java/ ← 테스트 코드
└── resources/
4. 의존성 관리 (dependencies)
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
5. 의존성 범위 (scope)
scope
| compile |
기본값, 항상 포함됨 |
| provided |
컴파일은 필요하지만 실행 시 제공됨 (ex: 서블릿 API) |
| runtime |
실행 시 필요 (ex: JDBC 드라이버) |
| test |
테스트 코드에서만 사용 (JUnit 등) |
6. 빌드 라이프사이클
Maven은 프로젝트를 빌드할 때 다음 일련의 단계(phase)를 거쳐 수행
단계 | 설명
| validate |
프로젝트 정보 확인 (pom.xml 검증 등) |
| compile |
소스 컴파일 |
| test |
단위 테스트 수행 |
| package |
.jar 또는 .war 패키징 |
| verify |
통합 테스트 등 결과 확인 |
| install |
로컬 저장소에 설치 (~/.m2/repository) |
| deploy |
원격 저장소에 배포 (CI/CD) |
실행 예시
mvn clean compile
mvn test
mvn package
mvn install
7. 플러그인 (plugins)
| 정의 |
빌드 작업을 구체적으로 수행하는 도구 (컴파일, 패키징 등) |
| 위치 |
<build><plugins> 태그 내부에 명시 |
예: Java 컴파일러 설정
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<source>21</source>
<target>21</target>
</configuration>
</plugin>
8. Maven 명령어 요약
명령어
| mvn clean |
target/ 디렉토리 삭제 |
| mvn compile |
소스 코드 컴파일 |
| mvn test |
테스트 코드 실행 |
| mvn package |
JAR/WAR 생성 |
| mvn install |
로컬 저장소에 패키지 설치 |
| mvn dependency:tree |
의존성 트리 출력 (중복 확인 등) |
9. 실무 팁
| Java 버전 호환 설정 |
maven-compiler-plugin의 source/target 설정 |
| 의존성 충돌 |
dependency:tree로 확인하고 직접 버전 지정 |
| 빌드 속도 개선 |
mvn package -DskipTests 등 사용 |
| 멀티 모듈 프로젝트 |
상위 pom.xml에서 공통 관리 (dependencyManagement) 사용 |
1. Gradle이란?
| 정의 |
Groovy 기반의 유연한 빌드 자동화 도구 |
| 특징 |
의존성 관리, 빌드/테스트/패키징 자동화, 태스크 기반 구조 |
| 장점 |
빠른 빌드 (Incremental), DSL 기반 구성, 커스터마이징 쉬움 |
2. 기본 구조: build.gradle
plugins {
id 'java'
}
group = 'com.example'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
implementation 'com.google.code.gson:gson:2.10.1'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
test {
useJUnitPlatform()
}
3. Gradle 프로젝트 구조
project/
├── build.gradle ← 빌드 스크립트
├── settings.gradle ← 멀티 프로젝트일 때 포함
└── src/
├── main/
│ └── java/
└── test/
└── java/
4. 의존성 관리
구문
| implementation |
컴파일 + 런타임 포함 (기본 의존성 범위) |
| api |
(라이브러리용) 의존성을 외부에도 노출 |
| compileOnly |
컴파일할 때만 필요 (provided와 유사) |
| runtimeOnly |
실행할 때만 필요 |
| testImplementation |
테스트 코드 전용 |
5. 태스크(Task) 시스템
| 정의 |
작업 단위: 컴파일, 테스트, 패키징 등 |
| 특징 |
태스크끼리 의존관계를 가짐 → 실행 순서 자동 결정 |
| 기본 태스크 |
compileJava, test, jar, build, clean 등 |
실행 예
gradle build // 전체 빌드
gradle test // 테스트 실행
gradle clean build // 빌드 전 정리 후 빌드
6. 사용자 정의 태스크
task hello {
doLast {
println 'Hello, Gradle!'
}
}
실행:
gradle hello
7. 주요 태스크 예시
태스크
| clean |
build/ 디렉토리 삭제 |
| compileJava |
자바 소스 컴파일 |
| test |
테스트 실행 |
| jar |
.jar 파일 생성 |
| build |
전체 빌드 수행 (compile + test + jar 포함) |
8. 빌드 속성 및 변수
def appName = 'MyApp'
version = '1.0.0'
task printVersion {
doLast {
println "App: ${appName}, Version: ${version}"
}
}
9. Gradle Wrapper
| 역할 |
Gradle 설치 없이 빌드 가능 (버전 고정 포함) |
| 생성 |
gradle wrapper 또는 gradle init 시 자동 생성 |
| 실행 |
./gradlew build (Linux/Mac), gradlew.bat build (Windows) |
10. Maven과 Gradle 비교 요약
항목 | Maven | Gradle
| 설정 방식 |
XML (pom.xml) |
Groovy/Kotlin (build.gradle) |
| 확장성 |
제한적 |
매우 유연 |
| 속도 |
느린 편 |
빠른 빌드 (증분 빌드, 캐시) |
| 의존성 선언 |
<dependency> 태그 |
implementation, testImplementation |
| 사용자 정의 |
제한적 |
자유로운 태스크 정의 |
11. 실무 팁
| 특정 버전 유지 |
Gradle Wrapper 사용 (./gradlew) |
| 멀티 모듈 관리 |
settings.gradle에서 모듈 등록 |
| 라이브러리 버전 통일 |
ext 또는 version catalog로 버전 변수화 |
| 테스트 제외 |
./gradlew build -x test |
1. 모듈 시스템이란?
| 도입 버전 |
Java 9 |
| 목적 |
코드 캡슐화, 의존성 명시, 런타임 최적화 |
| 핵심 |
module-info.java 파일을 통한 명시적인 모듈 선언과 연결 |
2. module-info.java 구성 예시
module com.example.myapp {
requires java.sql; // 다른 모듈에 의존
requires com.google.gson; // 외부 모듈 (예: gson)
exports com.example.service; // 외부에 공개할 패키지
}
3. 키워드 설명
키워드
| module |
모듈 선언 시작 |
| requires |
다른 모듈에 대한 의존성 선언 |
| exports |
외부에 공개할 패키지 지정 |
| opens |
리플렉션 접근 허용 (Spring, Jackson 등에서 필요) |
| uses / provides |
Service Loader 기반 의존 설정 |
4. 모듈 경로와 컴파일 방법
# 모듈 소스 구조 예
src/
├── com.example.myapp/
│ └── module-info.java
│ └── com/example/myapp/...
# 모듈 경로에서 컴파일
javac -d out --module-source-path src $(find src -name "*.java")
# 실행
java --module-path out -m com.example.myapp/com.example.myapp.Main
5. requires / exports / opens 예시
module com.example.data {
requires java.sql; // JDBC API 의존
exports com.example.data.model; // 공개할 패키지
opens com.example.data.entity; // 리플렉션 허용
}
6. 외부 모듈 추가 (예: Gson, JUnit 등)
방법
| Maven/Gradle 사용 |
Gradle에서는 modularity plugin 사용 |
| 수동 모듈 추가 |
.jar 파일을 모듈 디렉토리에 배치 후 --module-path 지정 |
| 자동 모듈명 부여 |
Automatic-Module-Name 또는 jar 이름으로 추정됨 |
--module-path lib/gson-2.10.1.jar
7. 자동 모듈 (Unnamed Module)
| 정의 |
module-info.java 없는 .jar → 자동 모듈 |
| 작동 방식 |
requires 자동모듈명;으로 인식됨 |
| 주의 |
이름 중복 시 충돌 가능, 정식 모듈로 대체 권장 |
8. 표준 Java 모듈 예시
모듈명
| java.base |
모든 Java 프로그램 기본 모듈 (자동 포함됨) |
| java.logging |
로그 API |
| java.sql |
JDBC |
| java.xml |
XML 처리 |
| java.desktop |
AWT, Swing GUI API |
| jdk.unsupported |
sun.misc 등 내부 API |
9. 실무 팁
| Spring, Jackson 등 사용하는 경우 |
opens로 리플렉션 허용 필요 |
| 외부 라이브러리 자동 모듈만 제공 |
--module-path와 requires 이름 일치 확인 |
| Gradle 모듈화 프로젝트 |
modularity 플러그인으로 자동 설정 가능 |
| 멀티 모듈 프로젝트 구성 |
settings.gradle, module-info.java 동시 관리 |
10. 모듈 시스템과 클래스패스 비교
기준 | 클래스패스 | 모듈 시스템
| 경로 |
-cp, CLASSPATH |
--module-path |
| 코드 접근 |
모든 클래스 접근 가능 |
exports 된 패키지만 접근 가능 |
| 이름 충돌 |
충돌 가능 |
명시적 이름 관리 |
| 보안/캡슐화 |
없음 |
강력한 캡슐화 제공 |
8단계. 테스트와 유지보수
| JUnit 5 |
단위 테스트, 어노테이션 (@Test, @BeforeEach 등) |
| Mockito |
목 객체를 이용한 테스트 |
| 로깅 |
SLF4J, Logback, 로그 레벨 설정 |
| 코드 품질 도구 |
Checkstyle, PMD, SpotBugs, SonarQube |
1. JUnit 5란?
| 정의 |
Java용 단위 테스트 프레임워크의 최신 버전 (JUnit 4 이후 완전 새로 설계됨) |
| 구성 |
JUnit Platform + JUnit Jupiter(작성 API) + JUnit Vintage(레거시 호환) |
| 특징 |
Java 8 이상 필요, 람다식/동적 테스트 등 현대적 기능 지원 |
2. 주요 어노테이션
| @Test |
하나의 테스트 메서드를 정의 |
| @BeforeEach |
각 테스트 메서드 실행 전마다 수행 |
| @AfterEach |
각 테스트 메서드 실행 후마다 수행 |
| @BeforeAll |
모든 테스트 시작 전 단 한 번 수행 (static 필요) |
| @AfterAll |
모든 테스트 끝난 후 단 한 번 수행 (static 필요) |
| @DisplayName("설명") |
테스트 메서드의 설명 출력 |
| @Disabled |
테스트 비활성화 (무시됨) |
| @Nested |
내부 클래스에서 계층형 테스트 가능 |
| @ParameterizedTest |
파라미터 기반 테스트 작성 |
| @Tag |
태깅 테스트 분류 (@Tag("slow")) |
3. JUnit 5 기본 예제
import org.junit.jupiter.api.*;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
Calculator calc;
@BeforeEach
void setUp() {
calc = new Calculator();
System.out.println("== 테스트 준비 완료 ==");
}
@AfterEach
void tearDown() {
System.out.println("== 테스트 종료 ==");
}
@Test
void testAdd() {
assertEquals(5, calc.add(2, 3));
}
@Test
@DisplayName("나누기 테스트")
void testDivide() {
assertThrows(ArithmeticException.class, () -> calc.divide(5, 0));
}
}
4. Assertions 종류
메서드
| assertEquals(a, b) |
a와 b가 같은지 비교 |
| assertTrue(condition) |
조건이 true인지 확인 |
| assertFalse(condition) |
조건이 false인지 확인 |
| assertNotNull(obj) |
객체가 null이 아님을 확인 |
| assertThrows() |
예외 발생 여부 확인 |
| assertAll() |
여러 개의 Assertion을 묶어 실행 |
| assertTimeout() |
시간 초과 테스트 |
5. 예외 테스트
@Test
void divideByZeroShouldThrow() {
assertThrows(ArithmeticException.class, () -> {
calculator.divide(10, 0);
});
}
6. 테스트 클래스/메서드 이름 전략
// 클래스: CalculatorTest
// 메서드: add_shouldReturnSum_whenTwoNumbersGiven
이렇게 네이밍하면 명확하게 문서처럼 읽히는 테스트.
7. 실행 방법(도구)
| IntelliJ |
오른쪽 ▶ 버튼 또는 전체 클래스 우클릭 → Run |
| Gradle |
./gradlew test |
| Maven |
mvn test |
| CLI |
JUnit Platform Console Launcher 사용 가능 |
8. build.gradle에 JUnit 5 추가
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
}
test {
useJUnitPlatform()
}
9. 실무 팁
| 매 테스트 전에 공통 객체 생성 |
@BeforeEach 활용 |
| 테스트 실행 순서 지정 필요 시 |
@TestMethodOrder + @Order(n) |
| 긴 테스트 분류 |
@Tag("slow") + 필터 실행 가능 |
| 반복 테스트 |
@RepeatedTest(n) |
| 다양한 입력값 테스트 |
@ParameterizedTest + @ValueSource |
1. Mockito란?
| 정의 |
**가짜 객체(Mock)**를 만들어서 의존성 격리 단위 테스트를 가능하게 해주는 프레임워크 |
| 목적 |
외부 시스템(DB, API 등)에 의존하지 않고 내부 로직만 테스트 |
| 사용 예 |
Repository, Service, DAO 등의 결과를 고정된 값으로 대체해서 테스트 |
2. Maven/Gradle 의존성
Gradle
testImplementation 'org.mockito:mockito-core:5.11.0'
testImplementation 'org.junit.jupiter:junit-jupiter:5.10.0'
testImplementation 'org.mockito:mockito-junit-jupiter:5.11.0'
3. 기본 구조 예제
예를 들어, UserService가 UserRepository에 의존한다고 가정.
실제 클래스
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository repo) {
this.userRepository = repo;
}
public String getUsername(Long id) {
User user = userRepository.findById(id);
return user != null ? user.getName() : "Unknown";
}
}
4. Mockito를 이용한 테스트 예제
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
import static org.junit.jupiter.api.Assertions.*;
class UserServiceTest {
UserRepository mockRepo;
UserService userService;
@BeforeEach
void setUp() {
mockRepo = mock(UserRepository.class);
userService = new UserService(mockRepo);
}
@Test
void testGetUsername_returnsCorrectName() {
// given (Mock 동작 설정)
when(mockRepo.findById(1L)).thenReturn(new User(1L, "Alice"));
// when
String name = userService.getUsername(1L);
// then
assertEquals("Alice", name);
}
@Test
void testGetUsername_returnsUnknownIfNull() {
when(mockRepo.findById(999L)).thenReturn(null);
String name = userService.getUsername(999L);
assertEquals("Unknown", name);
}
}
5. 자주 쓰는 Mockito 메서드
| mock(Class<T>) |
클래스의 가짜 객체 생성 |
| when().thenReturn() |
메서드 호출 결과를 지정 |
| verify(mock) |
해당 mock 객체가 호출됐는지 확인 |
| verify(mock, times(n)) |
n번 호출되었는지 검증 |
| doThrow().when(mock).method() |
예외 발생 시키기 |
| any(Class<T>) |
임의의 인자 매칭 (ex: anyLong()) |
6. @Mock, @InjectMocks, @ExtendWith 사용 (JUnit 5 연동)
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
UserRepository userRepository;
@InjectMocks
UserService userService;
@Test
void testUsername() {
when(userRepository.findById(1L)).thenReturn(new User(1L, "Bob"));
assertEquals("Bob", userService.getUsername(1L));
}
}
7. 실무 팁
| DB, API, 파일 등 외부 의존 제거 |
Repository, Service 등을 mock 처리 |
| 예외 상황 테스트 |
doThrow(new Exception()).when(mock)... |
| 객체 비교 시 |
.eq(), .any(), .same() 등의 matcher 사용 |
| void 메서드 테스트 |
doNothing().when(mock).voidMethod() 또는 verify() |
8. 함께 쓰면 좋은 도구
| assertj |
더 읽기 쉬운 Assertion 제공 |
| mockito-junit-jupiter |
JUnit 5와 Mockito 연동 지원 |
| @Spy |
실제 객체의 일부만 mock 처리할 때 사용 |
1. SLF4J란?
| 이름 |
Simple Logging Facade for Java |
| 역할 |
로깅 인터페이스만 제공 → 실제 구현체는 따로 존재 (Logback, Log4j 등) |
| 특징 |
로깅 구현체에 종속되지 않고, 통일된 API 사용 가능 |
예를 들어,
Logger logger = LoggerFactory.getLogger(MyClass.class);
→ Logback이든 Log4j든 설정만 바꾸면 바꿔치기 가능!
2. Logback이란?
| 정의 |
SLF4J의 공식 구현체, 매우 빠르고 유연함 |
| 구성 |
logback-classic, logback-core |
| 설정 파일 |
logback.xml 또는 logback-spring.xml |
| 장점 |
XML 기반 설정, 조건부 설정, 파일 롤링 등 지원 |
3. Maven/Gradle 설정
Gradle
dependencies {
implementation 'org.slf4j:slf4j-api:2.0.9'
implementation 'ch.qos.logback:logback-classic:1.4.11'
}
4. SLF4J 사용 예제
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MyApp {
private static final Logger log = LoggerFactory.getLogger(MyApp.class);
public static void main(String[] args) {
log.trace("Trace message");
log.debug("Debug message");
log.info("Info message");
log.warn("Warn message");
log.error("Error message");
}
}
5. 로그 레벨(Level)
레벨 | 설명
| TRACE |
가장 상세한 디버깅 정보 (로직 추적용) |
| DEBUG |
개발 단계에서 유용한 정보 |
| INFO |
실행 흐름 상 의미 있는 정보 (시작/종료 등) |
| WARN |
경고, 실행은 되지만 비정상 가능성 있음 |
| ERROR |
심각한 문제, 예외 발생 등 |
로그 레벨은 하위 포함 방식:
ERROR > WARN > INFO > DEBUG > TRACE
예: 로그 레벨을 INFO로 하면,
TRACE, DEBUG는 출력 안 됨.
6. logback.xml 기본 예시
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%d{HH:mm:ss}] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
7. 로깅 출력 위치
| ConsoleAppender |
콘솔에 출력 (기본) |
| FileAppender |
지정된 로그 파일로 출력 |
| RollingFileAppender |
일정 조건에 따라 파일 분리 저장 (시간, 크기 등) |
예: 파일로 출력하기
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>logs/app.log</file>
<encoder>
<pattern>%d %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
8. 로그 레벨 설정 예시
<root level="debug">
<appender-ref ref="STDOUT" />
</root>
<logger name="com.example" level="info" />
- root는 전체 로그 레벨
- logger name="패키지"로 개별 조정 가능
9. 실무 팁
| 운영에서는 INFO 이상만 보고 싶을 때 |
<root level="info"> |
| 특정 패키지만 디버깅하고 싶을 때 |
<logger name="com.example.service" level="debug"/> |
| 로그 파일을 일정 용량/일 단위로 분할 |
RollingFileAppender 설정 |
| Spring Boot에서 |
logback-spring.xml + application.yml에서 logging.level로 제어 가능 |
10. Spring Boot 예시 (application.yml)
logging:
level:
root: info
com.example.service: debug
file:
name: logs/app.log
1. 도구별 개요 비교
도구 | 주요 기능 | 주 사용 목적
| Checkstyle |
코드 스타일 검사 (코딩 규칙) |
네이밍, 들여쓰기, JavaDoc 등 스타일 체크 |
| PMD |
규칙 기반 정적 분석 |
불필요한 코드, 미사용 변수, 코드 스멜 |
| SpotBugs |
바이트코드 기반 버그 분석 |
NPE, equals/hashCode, 멀티스레드 문제 등 |
| SonarQube |
통합 품질 관리 도구 |
위 도구들 통합 + 대시보드, 품질게이트 |
2. Checkstyle
| 기능 |
자바 코드의 스타일 일관성을 검사 |
| 체크 예시 |
변수명 규칙, 중괄호 위치, 줄 길이, JavaDoc 여부 등 |
| 설정 파일 |
checkstyle.xml |
| 통합 도구 |
Gradle, Maven, IntelliJ, Eclipse |
Gradle 설정 예시
plugins {
id 'checkstyle'
}
checkstyle {
toolVersion = '10.12.3'
configFile = file("config/checkstyle/checkstyle.xml")
}
3. PMD
| 기능 |
코드 스멜, 불필요한 코드 탐지 |
| 체크 예시 |
미사용 변수, if 중복, 무한 루프, 복잡도 |
| 설정 파일 |
pmd-ruleset.xml |
| 통합 도구 |
Maven, Gradle, IntelliJ, SonarQube |
Gradle 설정 예시
plugins {
id 'pmd'
}
pmd {
toolVersion = '6.55.0'
ruleSets = [] // 비우고 커스텀 ruleset만 적용 가능
ruleSetFiles = files("config/pmd/pmd-ruleset.xml")
}
4. SpotBugs
| 기능 |
바이트코드 기반 정적 분석 도구 (FindBugs 후속) |
| 체크 예시 |
NPE, equals/hashCode 누락, 불변성 위반, 보안 취약성 등 |
| 통합 도구 |
Maven, Gradle, Eclipse, SonarQube |
| 설정 |
excludeFilter.xml로 제외할 버그 유형 지정 가능 |
Gradle 설정 예시 (with plugin)
plugins {
id 'com.github.spotbugs' version '5.0.14'
}
spotbugs {
toolVersion = '4.8.0'
effort = 'max'
reportLevel = 'low'
excludeFilter = file('config/spotbugs/excludeFilter.xml')
}
5. SonarQube
| 기능 |
종합 정적 분석 툴 (웹 대시보드 제공) |
| 특징 |
Checkstyle, PMD, SpotBugs 등을 통합 + 보안, 테스트 커버리지 등 |
| 실행 방식 |
서버 + 분석기 (sonar-scanner, Gradle 플러그인 등) |
| UI |
웹 브라우저로 코드 품질 추적 가능 (issues, coverage, duplication 등) |
Gradle 연동 예시
plugins {
id 'org.sonarqube' version '4.4.1.3373'
}
sonar {
properties {
property "sonar.projectKey", "myapp"
property "sonar.host.url", "http://localhost:9000"
property "sonar.login", "your-token"
}
}
서버 실행:
docker run -d -p 9000:9000 sonarqube:lts
웹 접속: http://localhost:9000
6. 도구별 추천 활용 방식 (도구 언제 쓰면 좋을까?)
| Checkstyle |
팀 코딩 컨벤션 통일이 중요할 때 |
| PMD |
반복 코드, 미사용 코드 제거에 유용 |
| SpotBugs |
실질적인 버그 가능성을 잡고 싶을 때 |
| SonarQube |
전체 품질 게이트/기준으로 관리할 때 (CI에 연동) |
7. 실무 팁
| PR 전에 품질 체크 |
Gradle check 태스크에 모든 분석 도구 통합 |
| CI 도입 시 |
GitHub Actions, Jenkins에서 sonar-scanner 연동 |
| 분석 제외 |
분석 제외 경로 (excludeFilter.xml, ruleset 등) 세팅 |
| 품질 기준 설정 |
SonarQube에서 품질 게이트(coverage ≥ 80%, duplication ≤ 3%) 설정 가능 |
9단계. Java 기반 프레임워크 & 실무 개발 환경
| JDBC |
DB 연결, SQL 실행, 트랜잭션 처리 |
| ORM & JPA |
Entity, Repository, JPQL |
| Spring Core |
DI, AOP, Bean 관리 |
| Spring Boot |
자동 설정, REST API, Web MVC |
| Maven/Gradle + Spring 연동 |
빌드 & 실행 |
| REST API 서버 개발 |
Controller, Service, DTO, Entity 설계 |
1. JDBC란?
| 정의 |
Java에서 DB에 접속하고 SQL을 실행하기 위한 API 표준 인터페이스 |
| 풀네임 |
Java DataBase Connectivity |
| 역할 |
Java 애플리케이션 ↔ DB 사이의 다리 역할 (Connection, Statement, ResultSet 등 사용) |
2. 기본 흐름 (전체 구조)
- 드라이버 로딩
- DB 연결 (Connection)
- SQL 작성 및 실행 (Statement, PreparedStatement)
- 결과 처리 (ResultSet)
- 자원 정리 (close)
- 트랜잭션 관리 (commit, rollback)
3. 기본 예제: DB 연결 & SELECT
import java.sql.*;
public class JdbcSelectExample {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/mydb"; // DB URL
String user = "root";
String password = "1234";
String sql = "SELECT id, name FROM users";
try (
Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery();
) {
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println("ID: " + id + ", 이름: " + name);
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4. INSERT/UPDATE/DELETE
String insertSql = "INSERT INTO users (name, email) VALUES (?, ?)";
try (
Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement pstmt = conn.prepareStatement(insertSql);
) {
pstmt.setString(1, "Alice");
pstmt.setString(2, "alice@example.com");
int rows = pstmt.executeUpdate(); // 영향받은 행 수 리턴
System.out.println("삽입된 행 수: " + rows);
}
5. 트랜잭션 처리
try (
Connection conn = DriverManager.getConnection(url, user, password);
) {
conn.setAutoCommit(false); // 수동 커밋
try (
PreparedStatement ps1 = conn.prepareStatement("INSERT INTO log (msg) VALUES (?)");
PreparedStatement ps2 = conn.prepareStatement("UPDATE users SET point = point - ? WHERE id = ?");
) {
ps1.setString(1, "포인트 차감 시도");
ps1.executeUpdate();
ps2.setInt(1, 10);
ps2.setInt(2, 1);
ps2.executeUpdate();
conn.commit(); // 모두 성공했을 때만 커밋
System.out.println("트랜잭션 성공!");
} catch (SQLException e) {
conn.rollback(); // 하나라도 실패하면 롤백
System.out.println("트랜잭션 롤백됨");
}
}
6. 주요 JDBC 객체 정리
| DriverManager |
DB 연결을 위한 드라이버 관리 클래스 |
| Connection |
실제 DB와 연결된 세션 객체 |
| PreparedStatement |
SQL 실행 객체 (파라미터 바인딩 가능) |
| ResultSet |
SELECT 결과를 담는 객체 |
| SQLException |
모든 JDBC 예외 처리 클래스 |
7. 실무 팁
| SQL Injection 방지 |
반드시 PreparedStatement 사용 |
| 연결 누수 방지 |
try-with-resources 사용하여 자동 close |
| 트랜잭션 처리 |
setAutoCommit(false) → commit() 또는 rollback() |
| JDBC 로깅 |
log4jdbc, p6spy 사용하면 SQL 확인 가능 |
| 커넥션 풀 |
직접 관리 어려울 땐 HikariCP, DBCP2, Spring JDBC 사용 고려 |
1. ORM이란?
| 정의 |
**객체(Object)**와 **관계형 DB 테이블(Relational)**을 자동으로 매핑해주는 기술 |
| 예시 |
Java 클래스 ↔ DB 테이블 / 필드 ↔ 컬럼 / 객체 ↔ 레코드 |
| 장점 |
SQL 직접 작성 최소화, 트랜잭션/커넥션 관리 자동화, 생산성 향상 |
2. JPA란?
| 정의 |
자바 ORM 표준 인터페이스, Hibernate가 대표 구현체 |
| 목적 |
자바 코드로 DB를 다루되, SQL 의존을 줄이고 객체지향적인 데이터 접근 |
| 구성 요소 |
@Entity, @Id, @GeneratedValue, EntityManager, JPQL, 트랜잭션 등 |
3. Entity 정의
import jakarta.persistence.*;
@Entity
@Table(name = "users") // 생략 가능
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) // AUTO, SEQUENCE 등도 가능
private Long id;
@Column(nullable = false)
private String name;
private String email;
// 기본 생성자 + getter/setter
}
| @Entity |
이 클래스는 테이블과 매핑된다는 의미 |
| @Table(name="...") |
테이블 이름 지정 |
| @Id |
기본 키 지정 |
| @GeneratedValue |
자동 생성 전략 지정 |
| @Column |
컬럼 속성 설정 |
4. Repository (Spring Data JPA 기준)
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
// 기본 제공 메서드: findAll(), findById(), save(), delete() 등
User findByEmail(String email); // 메서드 이름으로 자동 쿼리 생성됨
}
| CRUD |
save(), deleteById(), findAll() 등 자동 지원 |
| 쿼리 메서드 |
findByName(), findByAgeGreaterThan() 등 이름으로 자동 쿼리 |
| 커스텀 쿼리 |
@Query 또는 JPQL 사용 가능 |
5. JPQL (Java Persistence Query Language)
| 정의 |
엔티티 객체를 대상으로 하는 객체지향 쿼리 언어 |
| 예시 |
SELECT u FROM User u WHERE u.name = :name |
| 특징 |
SQL과 유사하지만, 테이블이 아닌 클래스/필드명 사용 |
@Query("SELECT u FROM User u WHERE u.email = :email")
User findByEmail(@Param("email") String email);
6. EntityManager 사용 예
(순수 JPA 코드에서 사용하는 방식)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("myPU");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
User user = new User();
user.setName("Alice");
em.persist(user); // INSERT
User found = em.find(User.class, user.getId()); // SELECT
em.getTransaction().commit();
em.close();
7. 주요 기능 요약
| 저장 |
save(entity) |
INSERT/UPDATE 자동 처리 |
| 조회 |
findById(id) |
PK 기반 단건 조회 |
| 조건 조회 |
findByName() |
이름 조건 자동 쿼리 |
| 삭제 |
deleteById() |
PK 기반 삭제 |
| JPQL |
@Query(...) |
SQL처럼 작성하지만 엔티티 기반 |
| 정렬/페이징 |
Pageable |
findAll(Pageable pageable) |
8. 실무 팁
| 다중 테이블 조회 |
JPQL JOIN 사용 (SELECT u FROM User u JOIN u.posts) |
| 복잡한 쿼리 |
@Query 또는 QueryDSL 사용 |
| 엔티티 ID 없이 INSERT 불가 |
@Id + 생성 전략 반드시 필요 |
| 성능 |
Lazy vs Eager 로딩 설정 신중하게 |
| 테스트 |
@DataJpaTest, H2, @Transactional 활용 |
9. JPA에서 중요한 개념 (요약)
| 엔티티 생명주기 |
persist, merge, detach, remove |
| Fetch 전략 |
EAGER, LAZY (N+1 주의) |
| 변경 감지 |
@Transactional + 엔티티 수정 → 커밋 시 자동 UPDATE |
| 트랜잭션 |
Spring에서는 @Transactional 필수 |
| ID 전략 |
IDENTITY, SEQUENCE, AUTO 등 DBMS에 따라 선택 |
1. DI (Dependency Injection, 의존성 주입)
| 정의 |
객체가 직접 의존 객체를 생성하지 않고, 외부에서 주입받는 방식 |
| 목적 |
객체 간 결합도 ↓, 테스트 용이성 ↑, 유연한 구조 설계 가능 |
| 방식 |
생성자 주입, 필드 주입, 세터 주입 등 |
@Component
public class OrderService {
private final UserRepository userRepository;
@Autowired // 생략 가능 (스프링 부트 2.6+ 이상)
public OrderService(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
2. IoC 컨테이너 & Bean
| IoC (제어의 역전) |
객체 생성과 생명주기 관리를 개발자가 아닌 스프링 컨테이너가 담당 |
| Bean |
스프링 IoC 컨테이너가 관리하는 객체 |
Bean 등록 방식
| 어노테이션 기반 |
@Component, @Service, @Repository, @Controller |
| 수동 등록 |
@Configuration + @Bean 조합 |
| XML 등록 (구식) |
<bean id="..." class="..."/> |
3. 주요 Bean 관련 어노테이션
| @Component |
일반적인 빈 |
| @Service |
서비스 계층 (의미 부여) |
| @Repository |
DAO 계층 (예외 변환 포함) |
| @Controller |
웹 계층 컨트롤러 |
| @Configuration |
설정 클래스 |
| @Bean |
직접 생성한 객체를 스프링 Bean으로 등록 |
| @Autowired |
자동 주입 |
| @Qualifier |
주입 시 이름 지정 |
| @Primary |
여러 Bean 중 기본으로 선택됨 |
4. AOP (Aspect-Oriented Programming)
| 정의 |
핵심 로직 외에 공통 부가기능(로깅, 트랜잭션 등)을 모듈화하는 기술 |
| 키워드 |
Aspect(모듈화 단위), JoinPoint(실행 지점), Advice(실행 로직), Pointcut(대상 지정) |
실용 예시
- 메서드 실행 전/후 로그
- 트랜잭션 시작/종료
- 메서드 호출 시간 측정
- 예외 로깅
AOP 코드 예시
@Aspect
@Component
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("[Before] 실행 메서드: " + joinPoint.getSignature());
}
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void logAfter(Object result) {
System.out.println("[AfterReturning] 반환 값: " + result);
}
}
5. DI 종류 요약 (방식)
| 생성자 주입 |
생성자 통해 주입 |
불변성 보장, 테스트 쉬움 |
| 필드 주입 |
@Autowired 직접 필드에 사용 |
간단하지만 테스트 어려움 |
| 세터 주입 |
setX() 통해 주입 |
선택적 의존성 시 유용 |
6. Bean 스코프
| singleton (기본) |
컨테이너당 1개 인스턴스 |
| prototype |
요청마다 새로 생성 |
| request |
웹 요청마다 생성 (Spring Web) |
| session |
HTTP 세션마다 생성 |
@Component
@Scope("prototype")
public class TempComponent { }
7. 실무에서 이렇게 사용돼
기능 | 기술 | 적용 위치
| 트랜잭션 |
@Transactional |
Service 계층 |
| 로깅 |
AOP @Before, @After |
공통 모듈 |
| DI |
@Autowired, 생성자 |
Controller → Service → Repository |
| Bean 관리 |
@Component, @Service, @Configuration |
모든 계층 |
8. DI → IoC → AOP 흐름 이해도
OrderController
↓
@Autowired OrderService
↓
OrderService → UserRepository
↓
UserRepository → DB
→ 이 모든 객체 생성과 주입을 Spring이 자동 관리 (IoC 컨테이너)
→ 그리고 AOP로 공통 로직(로깅, 트랜잭션)을 분리
9. 정리 표
| DI |
의존성 객체를 외부에서 주입 (객체 간 결합 낮춤) |
| IoC 컨테이너 |
객체의 생성/관리를 스프링이 담당 |
| Bean |
스프링이 관리하는 객체 |
| AOP |
공통 로직 분리 (로그, 트랜잭션 등) |
| @Component 계열 |
빈 자동 등록 |
| @Autowired |
빈 자동 주입 |
1. Spring Boot 자동 설정 (Auto Configuration)
| 정의 |
개발자가 별도로 설정하지 않아도, 상황에 맞는 Bean을 자동 구성해주는 기능 |
| 핵심 어노테이션 |
@SpringBootApplication (자동 설정 + 컴포넌트 스캔 포함) |
| 작동 원리 |
spring-boot-autoconfigure 안에 있는 @Conditional 기반 구성 |
| 대표 예시 |
DB 설정, WebMVC 설정, Jackson(ObjectMapper), Tomcat 내장 서버 등 자동 등록 |
코드 예시
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
SpringApplication.run(MyApp.class, args); // 내장 톰캣으로 실행됨
}
}
2. Spring Boot로 REST API 개발하기
| REST API |
자원 중심(URI)의 웹 API, JSON으로 데이터 송수신 |
| 기본 구조 |
Controller ↔ Service ↔ Repository ↔ DB |
| 특징 |
@RestController, @RequestMapping, @GetMapping, @PostMapping 사용 |
예제: 사용자 조회 API
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public ResponseEntity<UserDto> getUser(@PathVariable Long id) {
UserDto user = userService.getUser(id);
return ResponseEntity.ok(user);
}
@PostMapping
public ResponseEntity<Long> createUser(@RequestBody UserDto dto) {
Long id = userService.create(dto);
return ResponseEntity.status(HttpStatus.CREATED).body(id);
}
}
3. Web MVC 구조
Spring Boot는 내부적으로 Spring Web MVC를 사용해
HTTP 요청 처리 → 컨트롤러 → 서비스 → 리포지토리 → DB 흐름을 자동 구성해줘.
구성 흐름
[Client]
↓ HTTP 요청 (GET /api/users/1)
[DispatcherServlet] ← Spring Boot가 자동 등록
↓
[Controller] → @GetMapping("/api/users/{id}")
↓
[Service]
↓
[Repository] → DB (JPA, MyBatis 등)
↓
응답 객체 반환
↑
JSON 응답 (Jackson 자동 변환)
Web MVC 핵심 어노테이션
| @RestController |
JSON 반환하는 컨트롤러 (@Controller + @ResponseBody) |
| @RequestMapping |
URL 맵핑 기본 경로 설정 |
| @GetMapping, @PostMapping |
HTTP 메서드별 매핑 |
| @RequestBody |
요청 body의 JSON → 객체로 바인딩 |
| @PathVariable |
URL 경로 변수 추출 |
| @RequestParam |
쿼리 파라미터 받기 |
4. HTTP 상태 코드와 응답
ResponseEntity 사용 예
return ResponseEntity.status(HttpStatus.CREATED).body(id);
상태 코드 | 의미
| 200 OK |
요청 성공 |
| 201 Created |
자원 생성됨 |
| 204 No Content |
응답 바디 없음 |
| 400 Bad Request |
클라이언트 잘못된 요청 |
| 404 Not Found |
존재하지 않는 리소스 |
| 500 Internal Server Error |
서버 내부 오류 |
5. 자동 설정 파일들
파일 | 역할
| application.properties / application.yml |
환경 설정 파일 |
| 예시 |
|
server.port=8081
spring.datasource.url=jdbc:mysql://localhost:3306/mydb
spring.jpa.hibernate.ddl-auto=update
logging.level.org.springframework=DEBUG
6. JSON 직렬화 (Object ↔ JSON 변환)
기술 | 설명
| Jackson |
Spring Boot의 기본 JSON 직렬화 도구 |
| ObjectMapper |
내부적으로 @RestController의 return 값을 JSON으로 변환 |
7. 실무 구조 예시
com.example.myapp
├── controller
│ └── UserController.java
├── service
│ └── UserService.java
├── repository
│ └── UserRepository.java
├── domain
│ └── User.java
├── dto
│ └── UserDto.java
└── MyAppApplication.java
8. Spring Boot 자동 설정 대표 예시
구성 요소 | 자동 설정되는 것
| DB |
H2, MySQL, PostgreSQL 등 → DataSource, JdbcTemplate, JPA 자동 구성 |
| Web |
DispatcherServlet, Jackson, Embedded Tomcat |
| Validation |
@Valid, @Validated 자동 적용 |
| Static Resource |
/static, /public, /resources 하위 경로는 자동 정적 자원 제공 |
| Actuator |
/actuator/health, /metrics 등의 운영용 엔드포인트 제공 |
1. Maven + Spring Boot 연동
pom.xml 설정
<project xmlns="http://maven.apache.org/POM/4.0.0" ...>
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>spring-boot-maven-demo</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<java.version>17</java.version>
<spring.boot.version>3.2.4</spring.boot.version>
</properties>
<dependencies>
<!-- Spring Boot Starter -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Spring Boot Maven Plugin -->
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
실행 방법
# 빌드
mvn clean package
# 실행
java -jar target/spring-boot-maven-demo-1.0.0.jar
2. Gradle + Spring Boot 연동
build.gradle (Groovy DSL)
plugins {
id 'java'
id 'org.springframework.boot' version '3.2.4'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com.example'
version = '1.0.0'
java {
sourceCompatibility = JavaVersion.VERSION_17
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
Kotlin DSL을 사용하는 경우엔 build.gradle.kts로 작성됨
실행 방법
# 빌드
./gradlew build
# 실행
java -jar build/libs/프로젝트이름-1.0.0.jar
공통 실행 흐름 요약 (단계)
| clean |
기존 빌드 결과 삭제 |
| compile |
자바 소스 컴파일 |
| package / bootJar |
.jar 파일 생성 |
| java -jar |
내장 톰캣 포함된 JAR 실행 (Spring Boot 특징) |
실행 결과 예시
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.4)
Maven Gradle
| 빌드 배포용 JAR 생성 |
mvn package |
./gradlew build |
| 라이브러리 관리 |
pom.xml |
build.gradle(.kts) |
| CI/CD 자동화 |
GitHub Actions, Jenkins에서 실행 |
|
| 라이브러리 의존 자동 처리 |
spring-boot-starter-* |
|
1. 전반적인 레이어 구조
REST API 개발은 보통 아래와 같은 계층 구조를 가진다:
[요청(Request)]
↓
Controller ← HTTP 요청 처리, DTO로 데이터 전달
↓
Service ← 비즈니스 로직 담당
↓
Repository ← DB 접근 (JPA, MyBatis 등)
↑
Entity ← DB와 매핑되는 핵심 클래스
↑
DTO ← API 입출력 전용 데이터 객체
2. 패키지 구조 예시
com.example.app
├── controller ← @RestController
├── service ← @Service
├── repository ← JpaRepository
├── dto ← Request/Response DTO
├── domain (entity) ← @Entity
└── AppApplication.java
3. Entity 클래스
DB 테이블과 직접 매핑되는 객체
→ 비즈니스 핵심 도메인, JPA와 함께 사용됨
@Entity
@Table(name = "users")
public class User {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String email;
// 기본 생성자 + getter/setter
}
4. DTO (Data Transfer Object)
API 입출력용 객체 (Entity와 분리해야 한다!)
→ 계층 간 데이터 전달에 특화, 유효성 검사도 이쪽에서 수행
Request DTO
public class UserCreateRequest {
@NotBlank
private String name;
@Email
private String email;
}
Response DTO
public class UserResponse {
private Long id;
private String name;
private String email;
public UserResponse(User user) {
this.id = user.getId();
this.name = user.getName();
this.email = user.getEmail();
}
}
5. Service 계층
비즈니스 로직 담당
→ Controller와 Repository 사이의 중심 계층
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Long createUser(UserCreateRequest request) {
User user = new User();
user.setName(request.getName());
user.setEmail(request.getEmail());
return userRepository.save(user).getId();
}
public UserResponse getUser(Long id) {
User user = userRepository.findById(id)
.orElseThrow(() -> new RuntimeException("사용자 없음"));
return new UserResponse(user);
}
}
6. Controller 계층
HTTP 요청을 받고 → DTO로 변환하여 → Service 호출
→ JSON 응답 반환
@RestController
@RequestMapping("/api/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping
public ResponseEntity<Long> createUser(@RequestBody @Valid UserCreateRequest request) {
Long id = userService.createUser(request);
return ResponseEntity.status(HttpStatus.CREATED).body(id);
}
@GetMapping("/{id}")
public ResponseEntity<UserResponse> getUser(@PathVariable Long id) {
return ResponseEntity.ok(userService.getUser(id));
}
}
7. Repository
JPA를 기반으로 DB와 직접 소통하는 계층
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
}
8. 설계 원칙 요약
| Controller |
요청 매핑, DTO 변환, 응답 반환 |
| Service |
핵심 로직 처리, 트랜잭션 |
| DTO |
외부와의 데이터 전달 전용 |
| Entity |
DB와 직접 연결되는 객체 |
| Repository |
DB CRUD 처리 (JPA 등 사용) |
실무 설계 팁
| 요청/응답 데이터 분리 |
Entity ↔ DTO 변환 명확히 구분하기 |
| 재사용 |
DTO에 생성자 or Mapper 클래스 활용 |
| 예외 처리 |
@ControllerAdvice + @ExceptionHandler로 통합 처리 |
| 테스트 |
Service 단위 테스트, Controller는 @WebMvcTest |
10단계. 심화 학습
| 리플렉션 |
클래스 동적 생성 및 메소드 실행 |
| 애너테이션 |
커스텀 애너테이션, 처리기 |
| 직렬화 & 역직렬화 |
Serializable, JSON 변환 |
| 디자인 패턴 |
Singleton, Factory, Strategy, Observer 등 |
| 네트워크 프로그래밍 |
Socket, HTTP 통신 |
| 보안 개념 |
HTTPS, 인증, JWT, XSS/CSRF 방어 |
1. 리플렉션(Reflection)이란?
| 정의 |
컴파일된 클래스의 정보를 런타임에 동적으로 조회 및 조작할 수 있는 기능 |
| 위치 |
java.lang.reflect 패키지 |
| 용도 |
프레임워크 개발, 어노테이션 처리, 테스트 툴, 객체 자동 생성 등 |
| 핵심 클래스 |
Class, Field, Method, Constructor 등 |
2. 기본 사용 흐름
- 클래스 정보 얻기 → Class<?> clazz = Class.forName("패키지명.클래스명")
- 생성자, 필드, 메서드 등 접근 → clazz.getDeclaredMethod(...)
- 객체 생성 → constructor.newInstance()
- 메서드 실행 → method.invoke(instance, args...)
3. 예제 준비: 대상 클래스
package com.example;
public class HelloService {
private String message = "Hello, Reflection!";
public HelloService() { }
public void sayHello() {
System.out.println(message);
}
public void greet(String name) {
System.out.println("Hi " + name);
}
public String getMessage() {
return message;
}
private void secret() {
System.out.println("This is private!");
}
}
4. 클래스 동적 로딩 및 메서드 실행 예제
import java.lang.reflect.*;
public class ReflectionExample {
public static void main(String[] args) throws Exception {
// 1. 클래스 로딩
Class<?> clazz = Class.forName("com.example.HelloService");
// 2. 인스턴스 생성 (기본 생성자)
Object obj = clazz.getDeclaredConstructor().newInstance();
// 3. public 메서드 실행
Method method1 = clazz.getMethod("sayHello");
method1.invoke(obj); // 출력: Hello, Reflection!
// 4. 메서드에 인자 전달
Method method2 = clazz.getMethod("greet", String.class);
method2.invoke(obj, "Alice"); // 출력: Hi Alice
// 5. private 메서드 접근
Method secretMethod = clazz.getDeclaredMethod("secret");
secretMethod.setAccessible(true);
secretMethod.invoke(obj); // 출력: This is private!
}
}
5. 리플렉션 주요 클래스 요약
| Class<?> |
클래스 메타정보 |
| Method |
메서드 객체 (호출 가능) |
| Field |
멤버 변수 객체 (읽기/쓰기 가능) |
| Constructor |
생성자 객체 (newInstance로 생성 가능) |
6. 실무에서 리플렉션이 사용되는 예시
| DI 프레임워크 |
생성자/필드에 @Autowired가 붙은 객체를 자동 주입 |
| 테스트 도구 |
private 메서드 호출, 내부 상태 확인 |
| Jackson, Gson |
필드명을 기준으로 객체를 JSON으로 직렬화 |
| 스프링 AOP |
프록시 객체 내부에서 원본 클래스/메서드 호출 |
7. 리플렉션 사용 시 주의사항
| 성능 저하 |
리플렉션은 일반 메서드 호출보다 느림 |
| 캡슐화 침해 |
private 접근 무시 가능 (보안 위험) |
| 컴파일 시점 타입 체크 불가 |
런타임 에러 발생 위험 ↑ |
8. 필드 읽기/쓰기 예제
Field field = clazz.getDeclaredField("message");
field.setAccessible(true);
field.set(obj, "Changed Message");
Method sayHello = clazz.getMethod("sayHello");
sayHello.invoke(obj); // 출력: Changed Message
정리 요약
기능 | 클래스 | 메서드
| 클래스 정보 획득 |
Class<?> |
Class.forName("..."), obj.getClass() |
| 객체 생성 |
Constructor |
newInstance() |
| 메서드 호출 |
Method |
invoke() |
| 필드 접근 |
Field |
get(), set() |
| 비공개 접근 |
- |
setAccessible(true) |
1. 애너테이션(Annotation)이란?
| 정의 |
클래스, 메서드, 필드 등에 **추가 정보(메타데이터)**를 제공하는 문법 |
| 목적 |
컴파일러/프레임워크가 특정 처리를 하도록 유도 |
| 종류 |
내장 애너테이션, 사용자 정의 애너테이션 |
자주 쓰는 내장 애너테이션
| @Override |
부모 메서드를 재정의할 때 사용 |
| @Deprecated |
더 이상 사용되지 않음을 나타냄 |
| @SuppressWarnings |
경고 무시 |
| @FunctionalInterface |
함수형 인터페이스임을 명시 |
2. 커스텀 애너테이션 만들기
예시: 로그용 애너테이션 만들기
import java.lang.annotation.*;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecutionTime {
}
| @Target |
적용 대상 지정 (METHOD, TYPE, FIELD 등) |
| @Retention |
유지 범위 지정 (SOURCE, CLASS, RUNTIME) |
| @interface |
애너테이션 정의 구문 |
3. 커스텀 애너테이션 사용 예시
public class HelloService {
@LogExecutionTime
public void serve() {
System.out.println("서비스 로직 실행 중...");
}
}
4. 애너테이션 처리기 (리플렉션 기반)
import java.lang.reflect.Method;
public class AnnotationProcessor {
public static void main(String[] args) throws Exception {
HelloService service = new HelloService();
for (Method method : service.getClass().getDeclaredMethods()) {
if (method.isAnnotationPresent(LogExecutionTime.class)) {
long start = System.currentTimeMillis();
method.invoke(service); // 메서드 실행
long end = System.currentTimeMillis();
System.out.println("실행 시간: " + (end - start) + "ms");
}
}
}
}
5. 커스텀 애너테이션에 속성 추가
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface JsonName {
String value(); // 속성 추가
}
사용 예시:
public class User {
@JsonName("username")
private String name;
}
6. 실무에서 자주 쓰는 커스텀 애너테이션 예
| @Validated, @Valid |
입력 검증 |
| @Transactional |
트랜잭션 처리 |
| @Entity |
DB 테이블 매핑 |
| @RequestMapping |
URL 매핑 |
| @CustomSecurity |
자체 보안 처리 |
7. 실전 응용: @AdminOnly 애너테이션 만들기
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AdminOnly { }
처리기 예시:
if (method.isAnnotationPresent(AdminOnly.class)) {
if (!currentUser.isAdmin()) {
throw new SecurityException("관리자만 접근 가능");
}
}
정리 요약
| @interface |
애너테이션 정의 키워드 |
| @Target |
사용할 위치 지정 |
| @Retention |
언제까지 유지할지 지정 (RUNTIME은 실행 시 사용 가능) |
| 리플렉션 |
애너테이션 유무 확인 및 메타정보 처리 |
| 용도 |
검증, 로깅, 트랜잭션, 보안 등 프레임워크와 어울려 활용됨 |
1. 직렬화(Serialization)란?
| 정의 |
객체 → 바이트 형태로 변환하여 저장하거나 전송할 수 있도록 하는 과정 |
| 용도 |
파일 저장, 네트워크 전송, 세션 저장, 캐싱 등 |
| 대표 기술 |
Java 기본 Serializable, JSON 변환(Jackson, Gson) 등 |
2. 역직렬화(Deserialization)란?
| 정의 |
바이트 또는 문자열 형태의 데이터를 다시 객체로 복원하는 과정 |
| 전제 조건 |
직렬화된 데이터 + 클래스 정의 필요 |
3. Java 기본 직렬화 (implements Serializable)
클래스 정의
import java.io.Serializable;
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
// 생성자, getter, setter 생략
}
직렬화 → 파일 저장
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("user.dat"));
User user = new User("Alice", 25);
oos.writeObject(user);
oos.close();
역직렬화 → 객체 복원
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("user.dat"));
User readUser = (User) ois.readObject();
ois.close();
System.out.println(readUser.getName()); // "Alice"
4. Java 직렬화 주의사항
| serialVersionUID |
클래스 버전 정보, 변경되지 않으면 역직렬화 시 호환 가능 |
| transient |
직렬화에서 제외할 필드에 사용 |
| 보안 |
역직렬화 시 악의적 데이터로 공격 가능 (주의!) |
| 성능 |
무거움, JSON이 더 가볍고 다용도임 |
5. JSON 직렬화/역직렬화 (Jackson 사용)
JSON → 객체: 역직렬화
ObjectMapper mapper = new ObjectMapper();
String json = "{\"name\":\"Bob\",\"age\":30}";
User user = mapper.readValue(json, User.class);
System.out.println(user.getName()); // Bob
객체 → JSON: 직렬화
User user = new User("Alice", 28);
String json = mapper.writeValueAsString(user);
System.out.println(json); // {"name":"Alice","age":28}
6. Jackson의 특징
| 기본 클래스 |
ObjectMapper |
| 주요 기능 |
JSON ↔ 객체, 리스트 변환, 필드 무시, 날짜 처리 등 |
| 어노테이션 |
@JsonProperty, @JsonIgnore, @JsonFormat, @JsonInclude 등 |
예시: 필드 이름 변경
public class User {
@JsonProperty("user_name")
private String name;
}
7. 컬렉션 JSON 변환 예
List<User> users = List.of(new User("Tom", 20), new User("Jane", 21));
// 리스트 → JSON 배열
String jsonArray = mapper.writeValueAsString(users);
// JSON 배열 → 리스트
List<User> result = mapper.readValue(jsonArray,
new TypeReference<List<User>>() {});
정리 요약
방식 | 직렬화 | 역직렬화 | 특징
| Java Serializable |
ObjectOutputStream.writeObject() |
ObjectInputStream.readObject() |
빠르지만 무거움, 호환성 이슈 |
| JSON (Jackson) |
ObjectMapper.writeValueAsString() |
readValue() |
가볍고 범용성 ↑ |
실무 팁
| 파일 저장 |
Java 직렬화 사용 가능 (주의점 많음) |
| API 응답/요청 |
JSON + Jackson |
| 복잡한 데이터 전송 |
JSON 또는 ProtoBuf 추천 |
| 보안 필요 |
JSON은 안전하지만 검증 로직 필요 (Validator, DTO 등) |
1. Singleton 패턴 (단일 객체)
| 목적 |
객체를 오직 하나만 생성하고 공유해서 사용 |
| 사용처 |
DB 연결, 설정 클래스, 로거 등 |
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {} // 외부에서 new 불가
public static Singleton getInstance() {
return instance;
}
}
사용
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println(s1 == s2); // true
2. Factory 패턴 (객체 생성 책임 분리)
| 목적 |
객체 생성 로직을 별도의 팩토리 클래스로 분리 |
| 장점 |
새로운 객체 타입 추가 시 기존 코드를 수정하지 않음 (OCP 원칙) |
| 사용처 |
복잡한 객체 생성, 인터페이스 기반 구조 |
// 공통 인터페이스
interface Shape {
void draw();
}
// 구현 클래스
class Circle implements Shape {
public void draw() {
System.out.println("원 그리기");
}
}
class Square implements Shape {
public void draw() {
System.out.println("사각형 그리기");
}
}
// 팩토리 클래스
class ShapeFactory {
public static Shape getShape(String type) {
if (type.equals("circle")) return new Circle();
if (type.equals("square")) return new Square();
return null;
}
}
사용
Shape s = ShapeFactory.getShape("circle");
s.draw(); // 원 그리기
3. Strategy 패턴 (행위 전략 변경)
| 목적 |
알고리즘(전략)을 객체로 캡슐화하여 필요 시 교체 가능하게 만듦 |
| 장점 |
조건문 없이 전략 변경 가능 |
| 사용처 |
정렬, 출력 방식, 계산 로직 등 다양한 전략이 필요한 경우 |
// 전략 인터페이스
interface PaymentStrategy {
void pay(int amount);
}
// 전략 구현
class CreditCardPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println(amount + "원 신용카드 결제");
}
}
class KakaoPayPayment implements PaymentStrategy {
public void pay(int amount) {
System.out.println(amount + "원 카카오페이 결제");
}
}
// 컨텍스트
class PaymentService {
private PaymentStrategy strategy;
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void payNow(int amount) {
strategy.pay(amount);
}
}
사용
PaymentService service = new PaymentService();
service.setStrategy(new CreditCardPayment());
service.payNow(10000); // 10000원 신용카드 결제
service.setStrategy(new KakaoPayPayment());
service.payNow(5000); // 5000원 카카오페이 결제
4. Observer 패턴 (감시자/리스너)
| 목적 |
한 객체의 상태 변경을 다수의 객체에게 자동 전달 (알림 시스템) |
| 장점 |
느슨한 연결, 실시간 알림 구조 |
| 사용처 |
이벤트 리스너, 채팅방, 뉴스 구독 등 |
// 관찰자 인터페이스
interface Observer {
void update(String msg);
}
// 주제(Subject)
class ChatRoom {
private List<Observer> observers = new ArrayList<>();
public void join(Observer o) {
observers.add(o);
}
public void sendMessage(String msg) {
for (Observer o : observers) {
o.update(msg);
}
}
}
// 사용자(관찰자)
class User implements Observer {
private String name;
public User(String name) { this.name = name; }
public void update(String msg) {
System.out.println(name + "님에게 메시지: " + msg);
}
}
사용
ChatRoom room = new ChatRoom();
room.join(new User("홍길동"));
room.join(new User("이순신"));
room.sendMessage("안녕하세요!");
비교 요약
패턴 | 목적 | 주요 사용처
| Singleton |
객체 1개만 생성 |
Logger, 설정 클래스 |
| Factory |
객체 생성 로직 분리 |
서비스 객체 생성, 다양한 구현 제공 |
| Strategy |
알고리즘 교체 가능 |
결제 방식, 정렬 로직 |
| Observer |
상태 변경 알림 |
이벤트 리스너, 푸시 알림 |
실무 팁
- Spring에서도 많이 사용됨
- Singleton: 모든 Bean 기본 Scope
- Factory: BeanFactory, FactoryBean
- Strategy: 인터페이스 DI 후 실행
- Observer: @EventListener, ApplicationEventPublisher
1. 네트워크 프로그래밍이란?
| 정의 |
서로 다른 컴퓨터(프로세스)가 데이터를 주고받는 프로그램을 작성하는 것 |
| 방식 |
저수준: 소켓(Socket), 고수준: HTTP, REST API 등 |
| 필요성 |
서버-클라이언트 구조, 채팅, 웹 통신, IoT 등 다양한 분야에 활용됨 |
2. 소켓(Socket) 통신
TCP 기반 소켓 통신으로 두 장비 간 안정적인 연결을 통해 데이터를 송수신하는 방식
서버 측 코드 (ServerSocket 사용)
import java.io.*;
import java.net.*;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9999); // 포트 열기
System.out.println("서버 대기 중...");
Socket clientSocket = serverSocket.accept(); // 클라이언트 접속 대기
System.out.println("클라이언트 연결됨");
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String message = in.readLine();
System.out.println("받은 메시지: " + message);
out.println("서버 응답: " + message.toUpperCase());
clientSocket.close();
serverSocket.close();
}
}
클라이언트 측 코드 (Socket 사용)
import java.io.*;
import java.net.*;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 9999); // 서버 접속
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("hello server!");
String response = in.readLine();
System.out.println("서버로부터 응답: " + response);
socket.close();
}
}
Socket vs ServerSocket
| ServerSocket |
서버에서 연결을 수신 대기 (accept()) |
| Socket |
클라이언트에서 연결 시도 (new Socket(...)) |
3. HTTP 통신
3-1. HttpURLConnection (자바 기본)
import java.net.*;
import java.io.*;
public class HttpClient {
public static void main(String[] args) throws Exception {
URL url = new URL("https://jsonplaceholder.typicode.com/posts/1");
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
BufferedReader in = new BufferedReader(
new InputStreamReader(conn.getInputStream()));
String line;
while ((line = in.readLine()) != null) {
System.out.println(line);
}
in.close();
conn.disconnect();
}
}
3-2. HttpClient (Java 11 이상)
import java.net.http.*;
import java.net.URI;
public class ModernHttpClient {
public static void main(String[] args) throws Exception {
HttpClient client = HttpClient.newHttpClient();
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://jsonplaceholder.typicode.com/posts/1"))
.build();
HttpResponse res = client.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(res.body());
}
}
3-3. 외부 라이브러리 (실무에서는 주로 사용)
| OkHttp |
안드로이드/Java에서 많이 사용되는 경량 HTTP 클라이언트 |
| Apache HttpClient |
안정성과 기능이 뛰어남 |
| RestTemplate (Spring) |
Spring Boot에서 사용하는 REST 요청 도구 |
| WebClient (Spring WebFlux) |
비동기 HTTP 통신 지원 (Reactive) |
네트워크 프로그래밍 요약 비교
항목 | Socket | HTTP 통신
| 레벨 |
저수준 |
고수준 |
| 프로토콜 |
TCP (연결지향) |
HTTP (요청-응답 기반) |
| 실시간성 |
뛰어남 (채팅 등) |
한 방향 요청/응답 |
| 사용 예 |
채팅 서버, 게임 |
REST API, 웹 브라우저 |
실무 예시: 언제 어떤 걸 쓰나?
| 웹 API 요청 |
HttpClient, RestTemplate |
| 실시간 채팅 |
Socket, WebSocket |
| 원격 제어 프로그램 |
TCP 소켓 |
| DB나 API 연동 |
JSON + HTTP |
1. HTTPS (HyperText Transfer Protocol Secure)
| 정의 |
HTTP + TLS(SSL)을 조합한 암호화된 통신 프로토콜 |
| 목적 |
중간자 공격(MITM) 방지, 데이터 유출 차단 |
| 핵심 요소 |
인증서, 공개키/개인키 기반 암호화 |
| 적용 대상 |
모든 로그인, 결제, 민감정보 처리 페이지 무조건 HTTPS |
작동 흐름
- 클라이언트 → 서버에 접속 요청
- 서버 → SSL 인증서 제공
- 클라이언트 → 인증서 유효성 검증
- 양측 → 세션 키 협상
- 이후 통신은 세션 키로 대칭키 암호화
2. 인증(Authentication)
| 기본 인증 |
ID/PW 직접 전송 (보안 위험) |
| 세션 기반 인증 |
서버에 세션 저장, 클라이언트는 쿠키에 세션 ID 저장 |
| 토큰 기반 인증 (JWT) |
서버가 토큰 발급 → 클라이언트가 저장 → 요청 시 Authorization 헤더에 포함 |
3. JWT (JSON Web Token)
| 정의 |
서버가 클라이언트에게 발급하는 자기정보 포함형 토큰 |
| 특징 |
세션 저장 없이 인증 상태 유지 가능 (Stateless) |
| 형식 |
Header.Payload.Signature → Base64 인코딩된 문자열 |
구조
eyJhbGciOiJIUzI1NiJ9. ← Header
eyJ1c2VySWQiOjEsInJvbGUiOiJVU0VSIn0. ← Payload
sfsdkljfhlKJHE234rfa... ← Signature (검증용)
예제 (Spring 기준)
Authorization: Bearer eyJhbGciOiJIUzI1...
4. XSS (Cross-Site Scripting)
| 정의 |
악의적인 스크립트를 삽입해 사용자의 브라우저에서 실행하게 하는 공격 |
| 예시 |
댓글에 <script>alert("해킹")</script> 입력 후 실행됨 |
| 피해 |
쿠키 탈취, 계정 탈취, 화면 조작 등 |
방어 방법
| 출력 시 escape 처리 |
HTML 특수문자 변환 (< → <) |
| 템플릿 엔진 사용 |
JSP, Thymeleaf 등은 기본적으로 escape |
| CSP 정책 적용 |
Content-Security-Policy 헤더로 JS 출처 제한 |
5. CSRF (Cross-Site Request Forgery)
| 정의 |
사용자의 인증 정보를 악용해 의도치 않은 요청을 보내는 공격 |
| 피해 |
의도치 않은 결제/게시글 삭제/비밀번호 변경 등 |
작동 방식 예시
- 사용자가 로그인한 상태에서
- 악성 사이트 방문
- 해당 사이트가 사용자의 쿠키로 위조 요청 전송
방어 방법
| CSRF 토큰 검증 |
서버가 요청마다 고유 토큰 발급 및 확인 |
| Referer/Origin 검증 |
요청의 출처 헤더 검증 |
| SameSite 쿠키 속성 |
크로스 사이트 요청 차단 (SameSite=Lax or Strict) |
| POST/PUT 요청만 사용 |
GET 요청으로 상태 변경 막기 |
실무에서의 보안 체크리스트
| 모든 페이지 HTTPS 적용 |
SSL 인증서 설치 + 리디렉션 |
| 로그인 시 비밀번호 암호화 저장 |
BCrypt 등 해시 알고리즘 |
| JWT 유효기간, 서명 키 보안 |
짧은 만료, 비밀키 환경변수 처리 |
| 사용자 입력 escape 처리 |
XSS 방어 필수 |
| 상태 변경 요청에 CSRF 방어 적용 |
@EnableCsrf, csrfTokenRepository() 등 |
버전별 핵심 변화 요약
| Java 1.0 ~ 1.4 |
초창기 기본 기능 제공 (Object, String, Collection) |
| Java 5 (1.5) |
Generics, Enum, Annotation, Autoboxing, varargs 도입 |
| Java 6 |
성능 향상, 스크립팅 API 추가 (javax.script) |
| Java 7 |
try-with-resources, switch(String), NIO.2 파일 API |
| Java 8 |
✅ Lambda, Stream API, Functional Interface, Optional |
| Java 9 |
✅ 모듈 시스템 (JPMS) 도입 (module-info.java) |
| Java 10 |
var 키워드 (지역 변수 타입 추론) |
| Java 11 (LTS) |
HttpClient API 정식, JavaFX 분리 |
| Java 12~14 |
switch 개선, 텍스트 블록 미리보기 등 |
| Java 17 (LTS) |
✅ sealed class, pattern matching, 강력한 안정성 |
| Java 18~20 |
점진적 미리보기 기능 추가 |
| Java 21 (LTS) |
✅ Record, Pattern Matching 정식화, Virtual Thread |
Java 8 (중요)
| Lambda 표현식 |
함수형 스타일 프로그래밍 지원 |
| Stream API |
컬렉션 데이터 처리 간결화 |
| Functional Interface |
@FunctionalInterface + Predicate, Consumer 등 |
| Optional |
Null 처리에 안전한 타입 |
| Default method |
인터페이스에 기본 구현 가능 |
간단 예제
List<String> names = List.of("Alice", "Bob");
names.stream().filter(n -> n.startsWith("A")).forEach(System.out::println);
Java 9
| 모듈 시스템 (JPMS) |
module-info.java로 모듈화 개발 가능 |
| JShell |
Java REPL 도구 (실시간 테스트) |
| List.of() |
불변 컬렉션 생성자 |
Java 10
| var |
지역 변수 타입 추론 지원 (Java도 타입 추론 시작) |
var msg = "Hello"; // String으로 추론됨
Java 11 (LTS)
| HttpClient API 정식 |
비동기 HTTP 요청 가능 (java.net.http) |
| JavaFX 제거 |
별도 모듈로 분리됨 |
| 문자열 메서드 확장 |
isBlank(), repeat(), lines() 등 |
Java 14~16
| 텍스트 블록 (""") |
멀티라인 문자열 지원 |
| Pattern Matching (미리보기) |
instanceof 개선 |
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
Java 17 (LTS)
| Sealed class |
상속 제한 클래스 |
| Pattern Matching |
instanceof에서 자동 캐스팅 |
| Switch Expression (정식) |
switch도 값 반환 가능 |
int result = switch (value) {
case 1 -> 10;
case 2 -> 20;
default -> 0;
};
Java 18~20
| UTF-8 기본 인코딩 |
전역 기본이 UTF-8로 통일 |
| Structured Concurrency (미리보기) |
스레드 묶음 실행 지원 |
| Record Pattern (미리보기) |
Record에 Pattern Matching 적용 |
Java 21 (LTS)
| Virtual Thread (Project Loom) |
초경량 스레드 (Thread.startVirtualThread(...)) |
| Record Pattern 정식 |
record 분해 매칭 가능 |
| Sequenced Collection |
List 순서 명시적 지원 |
| String Templates |
${} 표현 지원 (미리보기) |
실무에서 중요하게 쓰는 변화 Top
| Java 5 |
제네릭, Enum |
타입 안정성, 열거상수 |
| Java 8 |
Lambda, Stream |
함수형 프로그래밍의 시작 |
| Java 9 |
모듈 시스템 |
대규모 시스템 구조화 |
| Java 11 |
HttpClient |
REST API 비동기 통신 |
| Java 17 |
Sealed, Switch 개선 |
문법 간결화, 안정성 향상 |
| Java 21 |
Virtual Thread |
고성능 서버 구현 가능 |