자바(SE)

[JDK] 버전별 특징 - JDK25

  • -

이번 포스트에서는 JDK 25의 new features에 대해 살펴보자.

Flexible Constructor Bodies (자유로운 생성자 본문) 

 

Flexible Constructor Bodies(자유로운 생성자 본문)

기존의 생성자에서는 다른 생성자를 호출할 때 맨 첫 번째 줄에서만 가능했다. 하지만 JDK25에 적용된 flexible constructor bodies는 이 제약이 없어졌다. 이를 통해 조상 상성자 호출 전에 파라미터를 검증하는 등의 동작이 가능해졌다.

public class Child extends Parent {
    public Child(int value) {
        // super() 호출 전 로직 가능!
        int validatedValue = validate(value); 
        super(validatedValue);
    }

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

 

Derived Record Creation

불변 객체인 Record를 수정해서 특정 필드만 다른 값으로 변경해서 사용할 수 있게 되었다. 이때 with 키워드를 사용한다.

record Point(int x, int y, int z) {}

Point p1 = new Point(1, 2, 3);

// x 값만 10으로 바꾸고 나머지는 p1에서 복사
Point p2 = p1 with { x = 10; }; 

System.out.println(p2); // Point[x=10, y=2, z=3]

 

Structured Concurency(구조화된 병렬성)

여러 개의 Virtual Thread 작업을 하나의 단위로 묶어서 관리할 수 있게 되었다. 작업 중 하나가 실패하면 다른 작업들도 자동으로 취소되어 리소스 누수를 방지한다.

public static virtualThreadTest() {
    try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
        // 여러 작업을 병렬로 시작
        Subtask<String> user = scope.fork(() -> findUser(userId));
        Subtask<Integer> order = scope.fork(() -> getOrderCount(userId));
        
        scope.join();           // 모든 작업 대기
        scope.throwIfFailed();  // 하나라도 실패하면 예외 발생
        
        // 결과값 사용
        System.out.println(user.get() + " has " + order.get() + " orders.");
    }
}

 

ScopedValue 클래스 추가

기존 ThreadLocal을 대체하기 위해 등장한 기능이다. 특히 수천, 수만 개의 가상 스레드를 사용할 때 메모리 효율성을 극대화하며, 데이터의 불변성을 보장하여 안전하게 데이터를 공유할 수 있게 합니다.

기존 방식으로 전통적인 메서드 호출을 살펴보자. 메서드 끼리 깊이 호출하는 상황에서 파라미터를 계속 전달하는 모습이 지금은 너무 당연해 보인다.

void 업무_시작(String user) {
    serviceA(user); // 사용하지도 않는데 넘겨줘야 함
}

void serviceA(String user) {
    serviceB(user); // 넘겨주기만 함 (귀찮음)
}

void serviceB(String user) {
    System.out.println("작업자: " + user); // 여기서만 사용
}

하지만 중간 메서드들 입장에서는 괜히 전달만 하고 있어서 답답해 보인다. 이런 상황에서 ScopedValues를 사용하면 코드가 깔끔해진다.

void 업무_시작() {
    ScopedValue.where(CONTEXT_USER, "Admin").run(() -> {
        serviceA(); // 파라미터가 깨끗함!
    });
}

void serviceA() {
    serviceB(); // 중간 단계에서 신경 쓸 필요 없음
}

void serviceB() {
    // 필요할 때만 쏙 꺼내 씀
    System.out.println("작업자: " + CONTEXT_USER.get());
}

이처럼 ScopedValue는 웹 서버의 인증 정보, 트랜잭션 컨텍스트, 로그 추적 ID처럼 애플리케이션 전반에 걸쳐 "현재 상태"를 공유해야 할 때 사용. 특히 가상 스레드를 많이 쓰는 현대적 자바 아키텍처에서 필수적인 도구라고 볼 수 있다.

 

Primitive types in patterns - JDK 23 이상에서 preview 설정 필요

instanceof, switch 문에서 int, double 등 primitive 타입을 더 자유롭게 사용할 수 있게 되었다. 이를 통해 정보 손실 없는 안전한 형변환(Lossless Conversion)성능 최적화를 이룰 수 있다. 

다음 예를 보면 형변환시 데이터 보호를 위해 어떻게 동작하는지 알 수 있다. 기존에는 primitive를 처리하기 위해 wrapper로 변경 후 확인했을 텐데 이제 그런 불필요한 변환 과정이 없어지게 된다.

double d = 10.0;
if (d instanceof int i) {
    // d가 10.0이므로 i는 10이 되고 매칭 성공!
}

double d2 = 10.5;
if (d2 instanceof int i) {
    // 10.5는 int로 바꾸면 0.5가 날아감(정보 손실). 
    // 따라서 매칭 실패! (false)
}

int i = 1000;
if (i instanceof byte b) { 
    // 1000은 byte(-128~127)에 못 담으므로 false
}

int j = 100;
if (j instanceof byte b) {
    // 100은 byte에 담기므로 true, b는 100이 됨
}

단 이 기능은 아직 확정은 아니어서 사용하려면 preview 옵션을 활성화 시켜야 한다.

 

Module Import Declarations (모듈 임포트 선언)

수많은 import 문을 작성하는 번거로움을 줄이기 위해, 모듈 전체가 제공하는 모든 패키지를 한 번에 임포트할 수 있는 기능이다.

// java.base 모듈의 모든 패키지(java.util, java.io, java.nio 등)를 한 번에 가져옴
import module java.base;

public class Main {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>(); // java.util 임포트 없이 사용 가능
    }
}

 

 

'자바(SE)' 카테고리의 다른 글

못말리는 소프트웨어 공학 동네 이야기  (0) 2025.10.31
Virtual Thread  (2) 2024.09.21
[JDK] 버전별 특징 - JDK21  (0) 2024.09.20
[Stream] 스트림의 병렬 처리  (1) 2024.07.08
[Thread] 05. 스레드 풀  (1) 2024.07.07
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.