본문 바로가기
Java

JAVA

by curious week 2025. 4. 7.

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. 실행 흐름 요약

  1. Java 소스 작성 (Hello.java)
  2. javac Hello.java → 바이트코드 파일 Hello.class 생성
  3. 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. 동작 구조 (간단 흐름)

  1. Thread A가 공유 객체의 synchronized 블록에 진입 → wait() 호출 → 대기 상태
  2. Thread B가 같은 공유 객체의 synchronized 블록에 진입 → 작업 수행 후 notify() 호출
  3. 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. 실행 흐름 요약

  1. Java 코드 컴파일 → .class 파일 생성
  2. 클래스 로더가 JVM에 클래스 로드
  3. 메서드 영역에 클래스 정보 저장
  4. main 메서드 실행 → 스택 프레임 생성
  5. new → 객체는 힙에 저장, 참조값은 스택에
  6. 명령어 흐름은 PC 레지스터가 추적
  7. 네이티브 메서드 호출 시 네이티브 메서드 스택 사용
  8. 객체가 사용되지 않으면 → 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 동작 예시 흐름

  1. new로 객체 생성 → Eden 영역에 저장
  2. Eden이 가득 차면 → Minor GC 발생
  3. 살아남은 객체는 → Survivor로 이동
  4. 여러 번 Minor GC 후에도 살아남으면 → Old로 승격
  5. 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 사용 예

  1. .class 또는 .jar 파일 열기
  2. GUI에서 클래스 구조와 메서드 내용을 Java 코드 형태로 확인 가능
  3. "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)

<groupId> 라이브러리 그룹명 (회사, 조직 등)
<artifactId> 모듈 이름
<version> 해당 라이브러리의 버전
중앙 저장소 Maven Central: https://search.maven.org

 

<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. 기본 흐름 (전체 구조)

  1. 드라이버 로딩
  2. DB 연결 (Connection)
  3. SQL 작성 및 실행 (Statement, PreparedStatement)
  4. 결과 처리 (ResultSet)
  5. 자원 정리 (close)
  6. 트랜잭션 관리 (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. 기본 사용 흐름

  1. 클래스 정보 얻기 → Class<?> clazz = Class.forName("패키지명.클래스명")
  2. 생성자, 필드, 메서드 등 접근 → clazz.getDeclaredMethod(...)
  3. 객체 생성 → constructor.newInstance()
  4. 메서드 실행 → 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

작동 흐름

  1. 클라이언트 → 서버에 접속 요청
  2. 서버 → SSL 인증서 제공
  3. 클라이언트 → 인증서 유효성 검증
  4. 양측 → 세션 키 협상
  5. 이후 통신은 세션 키로 대칭키 암호화

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 특수문자 변환 (< → &lt;)
템플릿 엔진 사용 JSP, Thymeleaf 등은 기본적으로 escape
CSP 정책 적용 Content-Security-Policy 헤더로 JS 출처 제한

5. CSRF (Cross-Site Request Forgery)

정의 사용자의 인증 정보를 악용해 의도치 않은 요청을 보내는 공격
피해 의도치 않은 결제/게시글 삭제/비밀번호 변경 등

작동 방식 예시

  1. 사용자가 로그인한 상태에서
  2. 악성 사이트 방문
  3. 해당 사이트가 사용자의 쿠키로 위조 요청 전송

방어 방법

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 고성능 서버 구현 가능

 

'Java' 카테고리의 다른 글

Javadoc  (1) 2025.06.21
Java sealed class  (0) 2025.04.04
java Record  (0) 2025.04.04
[Java] java 제어문  (5) 2025.01.17
[Java] 클래스 기본 구조  (4) 2025.01.16