Java 22-25 신규 기능 — Unnamed Variables부터 Primitive Patterns까지

Java 21 이후에도 진화는 계속된다

Java 21 LTS에서 Virtual Threads, Sequenced Collections, Record Patterns 등이 정식 출시되면서 큰 주목을 받았습니다. 하지만 그 이후 22~25에서도 개발자 경험을 크게 개선하는 기능들이 꾸준히 추가되고 있습니다.

이 글에서는 실무에서 바로 쓸 수 있는 정식 기능 과 알아두면 좋은 프리뷰 기능 을 구분해서 정리합니다.


Java 22 — 코드 정리의 미학

Unnamed Variables and Patterns (JEP 456) — 정식

사용하지 않는 변수를 _(언더스코어)로 표현할 수 있게 되었습니다. "이 변수는 의도적으로 무시합니다"라는 선언입니다.

JAVA
// Before: 사용하지 않는 변수에 이름을 지어야 했음
try {
    parseJson(input);
} catch (JsonException e) {   // e를 안 쓰는데 이름을 지어야 함
    return fallback;
}

// After: 명시적으로 무시
try {
    parseJson(input);
} catch (JsonException _) {   // "이 예외 객체는 안 씁니다"
    return fallback;
}

여러 곳에서 활용 가능합니다:

JAVA
// for-each에서 인덱스만 필요하고 값은 필요 없을 때
int count = 0;
for (var _ : collection) {
    count++;
}

// 람다에서 파라미터를 안 쓸 때
map.forEach((_, value) -> process(value));

// switch 패턴 매칭에서 특정 필드를 무시할 때
switch (shape) {
    case Circle(var radius)    -> computeCircle(radius);
    case Rectangle(var w, _)   -> computeWidth(w);  // 높이는 안 씀
    default -> throw new IllegalArgumentException();
}

하나의 스코프에서 _를 여러 번 사용할 수 있습니다. 일반 변수와 달리 이름 충돌이 발생하지 않습니다.

Statements before super() (JEP 447) — 프리뷰

생성자에서 super() 호출 전에 코드를 넣을 수 있게 되었습니다. 20년 넘게 "super()는 반드시 첫 줄"이었던 제약이 완화됩니다.

JAVA
// Before: super() 전에 인자 검증을 하려면 별도 static 메서드 필요
public class PositiveRange extends Range {
    public PositiveRange(int start, int end) {
        super(validate(start), validate(end));  // static 메서드로 우회
    }

    private static int validate(int value) {
        if (value < 0) throw new IllegalArgumentException();
        return value;
    }
}

// After: super() 전에 직접 검증 가능
public class PositiveRange extends Range {
    public PositiveRange(int start, int end) {
        if (start < 0 || end < 0) {
            throw new IllegalArgumentException("음수 불가");
        }
        super(start, end);  // 검증 후 호출
    }
}

**제한 **: super() 전에는 this와 인스턴스 필드를 참조할 수 없습니다. 객체가 아직 초기화되지 않았으니 당연합니다.

JAVA
public PositiveRange(int start, int end) {
    // ✅ OK: 지역 변수, static 메서드, 파라미터 사용
    int validatedStart = Math.max(0, start);

    // ❌ 컴파일 에러: 인스턴스 필드/메서드 참조 불가
    // this.someField = 10;
    // this.someMethod();

    super(validatedStart, end);
}

Java 23 — 표현력 확장

Primitive Type Pattern Matching (JEP 455) — 프리뷰

instanceofswitch에서 primitive 타입도 패턴 매칭 가능해집니다.

JAVA
// Before: switch에서 int 범위 매칭이 불가능
public String classify(int statusCode) {
    if (statusCode >= 200 && statusCode < 300) return "Success";
    if (statusCode >= 400 && statusCode < 500) return "Client Error";
    if (statusCode >= 500) return "Server Error";
    return "Unknown";
}

// After: switch 패턴 매칭으로 가독성 향상
public String classify(int statusCode) {
    return switch (statusCode) {
        case int s when s >= 200 && s < 300 -> "Success";
        case int s when s >= 400 && s < 500 -> "Client Error";
        case int s when s >= 500            -> "Server Error";
        default -> "Unknown";
    };
}

instanceof에서도 primitive 타입 사용 가능:

JAVA
// primitive narrowing을 안전하게 체크
Object value = getConfig("timeout");
if (value instanceof int timeout) {
    setTimer(timeout);
} else if (value instanceof long timeout) {
    setTimer((int) Math.min(timeout, Integer.MAX_VALUE));
}

Markdown Documentation Comments (JEP 467) — 프리뷰

JavaDoc에서 ** 마크다운 문법 **을 사용할 수 있습니다. ///(슬래시 3개)로 시작합니다.

JAVA
// Before: HTML 기반 JavaDoc
/**
 * Calculates the area of a circle.
 *
 * <p>The formula used is:</p>
 * <pre>{@code
 * area = π × radius²
 * }</pre>
 *
 * @param radius the radius of the circle
 * @return the area
 * @throws IllegalArgumentException if radius is negative
 */
public double circleArea(double radius) { ... }

// After: 마크다운 기반 JavaDoc
/// Calculates the area of a circle.
///
/// The formula used is:
///
/// ```
/// area = π × radius²
/// ```
///
/// @param radius the radius of the circle
/// @return the area
/// @throws IllegalArgumentException if radius is negative
public double circleArea(double radius) { ... }

@param, @return, @throws 같은 표준 태그는 그대로 사용합니다. 본문만 마크다운으로 작성하는 거죠. 코드 블록, 리스트, 링크, 강조 등을 HTML 없이 자연스럽게 표현할 수 있습니다.

Module Import Declarations (JEP 476) — 프리뷰

모듈 전체를 한 줄로 import할 수 있습니다.

JAVA
// Before: 여러 import문
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

// After: 모듈 단위 import
import module java.base;  // java.base 모듈의 모든 public 타입 사용 가능

프로토타이핑이나 학습용 코드에서 편리합니다. 프로덕션에서는 명시적 import가 더 나을 수 있습니다.


Java 24 — 안정화 & 강화

Stream Gatherers (JEP 485) — 정식 (2차 프리뷰에서 승격)

Java 22에서 프리뷰였던 Stream Gatherers가 Java 24에서 정식으로 안정화될 예정입니다.

JAVA
// 정식 API로 사용 가능
var batches = items.stream()
    .gather(Gatherers.windowFixed(100))
    .gather(Gatherers.mapConcurrent(5, this::process))
    .toList();

Stream Gatherers의 상세 내용은 별도 글을 참고하세요.

Structured Concurrency (JEP 480) — 프리뷰 유지

여러 차례 프리뷰를 거치며 API가 다듬어지고 있습니다.

JAVA
try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Subtask<User> user = scope.fork(() -> fetchUser(id));
    Subtask<List<Order>> orders = scope.fork(() -> fetchOrders(id));

    scope.join().throwIfFailed();
    return new UserDashboard(user.get(), orders.get());
}

Class-File API (JEP 484) — 정식

바이트코드를 읽고 쓰는 표준 API입니다. ASM 같은 외부 라이브러리 없이 .class 파일을 조작할 수 있습니다.

JAVA
// 클래스 파일 읽기
ClassModel cm = ClassFile.of().parse(bytes);
for (MethodModel method : cm.methods()) {
    System.out.println(method.methodName().stringValue());
}

// 클래스 파일 변환 (메서드 이름 변경 등)
byte[] newBytes = ClassFile.of().transformClass(cm,
    ClassTransform.dropping(me -> me instanceof MethodModel m
        && m.methodName().equalsString("deprecated")));

프레임워크/라이브러리 개발자에게 특히 유용합니다. Spring, Hibernate 같은 프레임워크가 내부적으로 활용할 수 있습니다.


Java 25 (LTS 예정) — 주목할 기능들

Java 25는 차기 LTS(Long-Term Support) 버전으로 예정되어 있어, 여기서 정식 출시되는 기능들은 실무에 즉시 영향을 줍니다.

Compact Source Files and Instance Main Methods (JEP 477)

Java 입문 장벽을 크게 낮추는 기능입니다.

JAVA
// Before: Hello World에 필요한 보일러플레이트
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, World!");
    }
}

// After: 최소한의 코드
void main() {
    println("Hello, World!");
}

public class, static, String[] args, System.out이 전부 생략 가능합니다. 교육 목적뿐 아니라 ** 스크립트 용도 **로도 Java를 쓸 수 있게 됩니다.

Scoped Values — 정식 출시 예상

여러 차례 프리뷰를 거친 Scoped Values가 Java 25 LTS에서 정식이 될 가능성이 높습니다.

Scoped Values의 상세 내용은 별도 글을 참고하세요.

Project Valhalla — Value Types (진행 중)

primitive 타입처럼 동작하면서 메서드를 가질 수 있는 "Value Type"을 목표로 합니다. Integer의 boxing 오버헤드를 제거하면서도 제네릭에서 사용 가능하게 만드는 프로젝트입니다.

JAVA
// 미래: Value Type (문법은 변경될 수 있음)
value class Point {
    int x;
    int y;
}
// 힙 할당 없이 인라인으로 저장
// List<Point>에서도 boxing 오버헤드 없음

아직 프리뷰 단계에 진입하지 못했지만, Java의 미래를 결정지을 핵심 프로젝트입니다.


실무에서 바로 쓸 수 있는가?

기능버전상태실무 적용
Unnamed Variables (_)22** 정식**✅ 바로 사용 가능
Statements before super()22프리뷰⚠️ --enable-preview 필요
Primitive Patterns23프리뷰⚠️ --enable-preview 필요
Markdown JavaDoc23프리뷰⚠️ --enable-preview 필요
Module Import23프리뷰⚠️ --enable-preview 필요
Stream Gatherers24** 정식**✅ 바로 사용 가능
Class-File API24** 정식**✅ 라이브러리 개발에
Compact Source Files25** 정식 예상**✅ 스크립트/교육

프리뷰 기능은 --enable-preview 플래그가 필요하므로 프로덕션에서는 신중하게 판단해야 합니다. 하지만 ** 다음 LTS(Java 25)에서 정식이 될 기능들 **을 미리 알아두면 마이그레이션 준비에 도움이 됩니다.


버전 업그레이드 전략

현재 Java 21 LTS를 쓰고 있다면

  1. Unnamed Variables: Java 22로 올리면 바로 쓸 수 있는 가장 실용적인 기능
  2. Stream Gatherers: Java 24에서 정식 — 복잡한 스트림 처리가 많다면 업그레이드 동기가 됨
  3. Java 25 LTS: 다음 LTS에서 Scoped Values, Compact Source Files 등이 정식화 예상 — LTS 간 점프 계획 수립

새 프로젝트를 시작한다면

Java 25 LTS가 출시되면 바로 채택하는 게 이상적입니다. 그 전까지는 Java 21 LTS가 가장 안정적인 선택입니다.


정리

Java 22-25는 "문법 편의성(Unnamed Variables, Compact Source Files)"과 "표현력 확장(Primitive Patterns, Gatherers)"이라는 두 축으로 진화하고 있습니다.

버전가장 주목할 기능한 줄 요약
Java 22Unnamed Variables안 쓰는 변수를 _로 명시
Java 23Markdown JavaDocHTML 대신 마크다운으로 문서화
Java 24Stream Gatherers 정식커스텀 중간 연산 시대
Java 25Compact Source Filesvoid main() {} 시대

공부하면서 느낀 건, 각각의 기능이 작아 보여도 모이면 ** 개발자 경험이 극적으로 달라진다 **는 점입니다. Unnamed Variables 하나만으로도 catch 블록, 람다, for-each에서 코드가 훨씬 깔끔해집니다.

댓글 로딩 중...