Java 22-25 신규 기능 — Unnamed Variables부터 Primitive Patterns까지
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) — 정식
사용하지 않는 변수를 _(언더스코어)로 표현할 수 있게 되었습니다. "이 변수는 의도적으로 무시합니다"라는 선언입니다.
// Before: 사용하지 않는 변수에 이름을 지어야 했음
try {
parseJson(input);
} catch (JsonException e) { // e를 안 쓰는데 이름을 지어야 함
return fallback;
}
// After: 명시적으로 무시
try {
parseJson(input);
} catch (JsonException _) { // "이 예외 객체는 안 씁니다"
return fallback;
}
여러 곳에서 활용 가능합니다:
// 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()는 반드시 첫 줄"이었던 제약이 완화됩니다.
// 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와 인스턴스 필드를 참조할 수 없습니다. 객체가 아직 초기화되지 않았으니 당연합니다.
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) — 프리뷰
instanceof와 switch에서 primitive 타입도 패턴 매칭 가능해집니다.
// 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 타입 사용 가능:
// 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개)로 시작합니다.
// 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할 수 있습니다.
// 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에서 정식으로 안정화될 예정입니다.
// 정식 API로 사용 가능
var batches = items.stream()
.gather(Gatherers.windowFixed(100))
.gather(Gatherers.mapConcurrent(5, this::process))
.toList();
Stream Gatherers의 상세 내용은 별도 글을 참고하세요.
Structured Concurrency (JEP 480) — 프리뷰 유지
여러 차례 프리뷰를 거치며 API가 다듬어지고 있습니다.
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 파일을 조작할 수 있습니다.
// 클래스 파일 읽기
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 입문 장벽을 크게 낮추는 기능입니다.
// 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 오버헤드를 제거하면서도 제네릭에서 사용 가능하게 만드는 프로젝트입니다.
// 미래: Value Type (문법은 변경될 수 있음)
value class Point {
int x;
int y;
}
// 힙 할당 없이 인라인으로 저장
// List<Point>에서도 boxing 오버헤드 없음
아직 프리뷰 단계에 진입하지 못했지만, Java의 미래를 결정지을 핵심 프로젝트입니다.
실무에서 바로 쓸 수 있는가?
| 기능 | 버전 | 상태 | 실무 적용 |
|---|---|---|---|
Unnamed Variables (_) | 22 | ** 정식** | ✅ 바로 사용 가능 |
| Statements before super() | 22 | 프리뷰 | ⚠️ --enable-preview 필요 |
| Primitive Patterns | 23 | 프리뷰 | ⚠️ --enable-preview 필요 |
| Markdown JavaDoc | 23 | 프리뷰 | ⚠️ --enable-preview 필요 |
| Module Import | 23 | 프리뷰 | ⚠️ --enable-preview 필요 |
| Stream Gatherers | 24 | ** 정식** | ✅ 바로 사용 가능 |
| Class-File API | 24 | ** 정식** | ✅ 라이브러리 개발에 |
| Compact Source Files | 25 | ** 정식 예상** | ✅ 스크립트/교육 |
프리뷰 기능은
--enable-preview플래그가 필요하므로 프로덕션에서는 신중하게 판단해야 합니다. 하지만 ** 다음 LTS(Java 25)에서 정식이 될 기능들 **을 미리 알아두면 마이그레이션 준비에 도움이 됩니다.
버전 업그레이드 전략
현재 Java 21 LTS를 쓰고 있다면
- Unnamed Variables: Java 22로 올리면 바로 쓸 수 있는 가장 실용적인 기능
- Stream Gatherers: Java 24에서 정식 — 복잡한 스트림 처리가 많다면 업그레이드 동기가 됨
- 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 22 | Unnamed Variables | 안 쓰는 변수를 _로 명시 |
| Java 23 | Markdown JavaDoc | HTML 대신 마크다운으로 문서화 |
| Java 24 | Stream Gatherers 정식 | 커스텀 중간 연산 시대 |
| Java 25 | Compact Source Files | void main() {} 시대 |
공부하면서 느낀 건, 각각의 기능이 작아 보여도 모이면 ** 개발자 경험이 극적으로 달라진다 **는 점입니다. Unnamed Variables 하나만으로도 catch 블록, 람다, for-each에서 코드가 훨씬 깔끔해집니다.