자바 언어·플랫폼 학습 노트 목차

모듈과 모던 자바 — 언어는 계속 진화한다

큰 코드를 어떻게 경계 지어 관리하고, 자바가 어떻게 더 안전하고 간결해졌는지. 모듈 시스템에서 시작해 record·sealed·패턴 매칭, 그리고 Java 21/25의 새 무기까지 — 왜 이런 기능이 생겼는지와 함께 본다.


1. 경계 긋기 — 모듈 시스템(JPMS)

public이면 어디서든 쓸 수 있다는 게 큰 시스템에선 약점이다. 내부 구현이 의도치 않게 외부에 새어 결합이 생긴다. 그래서 Java 9는 패키지 위에 모듈이라는 더 큰 경계를 뒀다.

// module-info.java
module com.shop.order {
    requires com.shop.common;     // 무엇에 의존하는가
    exports com.shop.order.api;   // 무엇을 공개하는가 (이것 외엔 전부 숨김)
    opens   com.shop.order.entity;// 리플렉션(JPA·Jackson)에만 연다
}

핵심은 강한 캡슐화exports하지 않은 패키지는 public이어도 모듈 밖에서 아예 안 보인다. JVM은 시작 시 requires를 따라 모듈 그래프를 해석해 경계를 강제한다.

실무에서는 — 꼭 모듈로 안 나눠도, jlink앱이 실제 쓰는 모듈만 묶어 경량 런타임을 만들면 컨테이너 이미지가 확 줄어든다. Java 25의 import module java.base;는 모듈이 공개한 패키지를 한 줄로 임포트하게 해 준다.


2. 데이터는 record로 — 보일러플레이트의 끝

값을 담는 클래스마다 생성자·접근자·equals·hashCode·toString을 손으로 쓰는 건 실수의 온상이었다(01의 hashCode 누락 버그처럼). record가 이걸 자동화한다.

record Range(int lo, int hi) {
    Range { if (lo > hi) throw new IllegalArgumentException(); }  // 컴팩트 생성자로 검증
}

컴파일러가 private final 필드 + 표준 생성자 + 접근자 + equals/hashCode/toString을 만들어 준다. 클래스파일에 Record 속성이 남아 리플렉션·패턴 매칭이 이를 활용한다.

실무에서는 — DTO, API 요청/응답, 다중 반환값, 맵 키 같은 값 객체는 record가 사실상 표준이다. 불변이라 스레드 안전·키 안전도 공짜로 따라온다.


3. 닫힌 계층 — sealed + 패턴 매칭

타입의 가능한 경우가 정해져 있다면(예: 도형은 원/사각형뿐), 그걸 컴파일러에게 알려 주는 게 sealed다.

sealed interface Shape permits Circle, Rectangle {}
record Circle(double r) implements Shape {}
record Rectangle(double w, double h) implements Shape {}

permits로 하위 타입을 못 박으면(클래스파일 PermittedSubclasses 속성), switch모든 경우를 다뤘는지 검사해 준다 — 새 타입을 빠뜨리면 컴파일 오류가 나니, 누락 버그가 사라진다.

String describe(Shape s) {
    return switch (s) {
        case Circle(double r) when r > 10 -> "큰 원";        // 가드
        case Circle(double r)             -> "원 r=" + r;     // record 디컨스트럭션
        case Rectangle(double w, double h)-> "사각형 " + w*h;
    };  // sealed라 망라 → default 불필요
}

실무에서는 — sealed + record + switch 조합은 상태/이벤트/결과를 모델링하는 강력한 도구다. 예: sealed interface Result permits Ok, Err로 성공/실패를 타입으로 표현하면, 예외 대신 값으로 다루며 컴파일러가 두 경우를 다 처리했는지 보장한다.


4. 더 안전하고 간결해진 문법

String sql = """
        SELECT id FROM users
        WHERE active = true
        """;                         // text block: 여러 줄 문자열, 들여쓰기 자동 정리

var list = new ArrayList<String>();  // var: 우변으로 타입 추론(지역변수만)

int code = switch (status) {         // switch 식: 값을 반환, 폴스루 없음(안전)
    case ACTIVE, PENDING -> 200;
    case CLOSED          -> 410;
};

이들은 전부 실수 줄이기읽기 쉬움을 향한다 — switch 식은 폴스루 버그가 없고, text block은 문자열 이어붙이기 실수를 없앤다.


5. Java 21 / 25의 새 무기

기능 (JEP)한 줄 · 어디에
Virtual Threads (444, 21)블로킹 코드로 대규모 동시성 → I/O 많은 서버(동시성 페이지)
Sequenced Collections (431, 21)getFirst/getLast/reversed 통일
Scoped Values (506, 25)ThreadLocal의 불변·자동 해제 대체 → 요청 컨텍스트 전파
Stable Values (502, 25)지연 1회 초기화 상수 → 무거운 싱글톤·캐시
Module Import (511, 25)import module … 일괄 임포트
Compact Source + Instance main (512, 25)클래스 없이 void main() → 입문·스크립트
Compact Object Headers (519, 25)객체 헤더 12→8B → 힙 절약(JVM 페이지)
// Stable Values (JEP 502): final 불변성 + 지연 초기화 — 홀더/이중검사잠금을 대체
private final StableValue<Config> cfg = StableValue.of();
Config cfg() { return cfg.orElseSet(this::loadConfig); }   // 최초 1회만 load

실무에서는 — 새 LTS(21·25)의 무게중심은 동시성(가상 스레드·구조적 동시성·Scoped Values)과 메모리/기동(Compact Headers·AOT)이다. 블로킹 I/O 위주 서버라면 가상 스레드 전환이 가장 체감이 크고, 컨테이너 밀도를 높이려면 Compact Object Headers가 조용히 힙을 아껴 준다.


한눈에 정리

  • 모듈(JPMS): 패키지 위의 경계. exports 안 하면 public이어도 숨김. jlink로 경량 런타임.
  • record: 값 객체의 보일러플레이트 자동화 → DTO·키·다중반환. 불변.
  • sealed + 패턴 매칭: 닫힌 계층 → switch 망라 검사. 상태/결과 모델링(Result 타입).
  • 간결 문법: text block·var·switch 식 — 실수 줄이고 읽기 쉽게.
  • 21/25: 동시성(가상스레드·ScopedValues)·메모리(StableValues·Compact Headers)가 핵심.
GC·JIT는 무대 뒤에서 — 내부와 실무 튜닝실무 노하우·실제 사고 사례