JDK 23의 특징과 새로운 기능, 어떤 점이 달라졌을까?

JDK 23의 특징과 새로운 기능, 어떤 점이 달라졌을까?

JDK 23의 특징과 새로운 기능, 어떤 점이 달라졌을까?

Java 개발자라면 매년 새로운 JDK 버전이 나올 때마다 어떤 변화가 있는지 궁금해지기 마련입니다. 작년에 나온 JDK 23도 예외는 아니에요. 지난 2024년 9월에 정식 릴리스된 이 버전은 몇 가지 흥미로운 기능과 개선점을 가져왔는데, 단순히 최신 트렌드를 쫓는 데 그치지 않고 개발 경험을 더 효율적이고 유연하게 만들어주는 데 초점을 맞춘 느낌입니다. 이번 포스팅에서는 JDK 23의 주요 특징과 새로 추가된 기능을 하나씩 살펴보면서, 어떤 점이 실무에 도움이 될지 고민해보려고 합니다. 자바를 다루는 분들이라면 한 번쯤 읽어보고 직접 써보는 것도 나쁘지 않을 것 같아요.


1. 기본형 타입의 패턴 매칭 지원

JDK 23에서 눈에 띄는 변화 중 하나는 기본형 타입(Primitive Types)을 패턴 매칭에서 사용할 수 있게 됐다는 점이에요. 원래 Java에서 `instanceof`나 `switch` 같은 패턴 매칭은 참조 타입(Reference Types)에만 적용됐었죠. 하지만 이번 업데이트로 `int`, `long`, `float`, `double`, `boolean` 같은 기본형 타입도 지원하게 됐습니다. 이게 왜 중요한지 예를 들어 볼까요?

public void checkValue(Object obj) {
    if (obj instanceof Integer i && i > 0) {
        System.out.println("양수입니다: " + i);
    } else if (obj instanceof Integer i) {
        System.out.println("0 또는 음수입니다: " + i);
    }
}

이제 이런 식으로 기본형 값이 포함된 객체를 더 자연스럽게 다룰 수 있어요. 물론 JDK 23에서는 이 기능이 아직 미리보기(Preview) 단계라 정식 채택까지는 시간이 좀 더 걸릴지도 모르지만, 코드 가독성과 유연성을 높이는 데 꽤 유용해 보입니다. 특히 `switch` 문에서도 기본형을 활용한 패턴 매칭이 가능해지면, 조건 분기 로직이 훨씬 깔끔해질 거예요.

예를 하나 더 들어보면:

public String describeNumber(double value) {
    return switch (value) {
        case double d when d > 0 -> "양수: " + d;
        case double d when d < 0 -> "음수: " + d;
        default -> "0이네요";
    };
}

이렇게 작성하면 기존의 복잡한 `if-else` 문 대신 더 간결하고 직관적인 코드를 만들 수 있죠. 아직 실험적인 기능이라 프로덕션 코드에 바로 적용하기는 조심스럽겠지만, 테스트 환경에서 한 번쯤 돌려보면서 감을 익혀두는 것도 좋을 것 같아요.


2. 모듈 가져오기 선언(Module Import Declarations)

다음으로 주목할 만한 건 모듈 가져오기 선언(Module Import Declarations)이 새로 추가됐다는 점입니다. Java 9에서 모듈 시스템이 도입된 이후로, 모듈 단위로 코드를 관리하는 게 점점 익숙해지고 있죠. 그런데 매번 필요한 패키지를 하나씩 `import`하는 게 귀찮을 때가 많았어요. JDK 23에서는 이런 불편함을 덜어주기 위해 모듈 전체를 한 번에 가져올 수 있는 기능을 넣었어요.

import module java.base;

이 한 줄이면 `java.base` 모듈에 포함된 모든 패키지를 사용할 수 있게 됩니다. 예를 들어 `java.lang`, `java.util`, `java.io` 같은 기본 패키지를 따로 임포트하지 않아도 코드가 동작해요. 이 기능은 특히 소규모 프로젝트나 학습용 코드에서 유용할 것 같아요. 패키지 관리에 신경 쓸 필요 없이 핵심 로직에 집중할 수 있으니까요.

다만, 이 기능에도 몇 가지 주의할 점이 있어요. 모듈 전체를 불러오다 보면 이름 충돌(Name Collision)이 생길 가능성이 커질 수 있죠. 예를 들어, 여러 모듈에서 동일한 클래스 이름이 존재한다면 컴파일러가 혼란스러워할 수도 있어요. 그래서 대규모 프로젝트에서는 여전히 세부적으로 패키지를 지정하는 게 안전할 수 있습니다. 이 기능도 미리보기 상태라, 실제로 써보고 피드백을 남기면 앞으로 더 다듬어질 가능성이 높아요.


3. JavaDoc에 마크다운 적용하기

JDK 23에서 개인적으로 꽤 반가운 소식 중 하나는 JavaDoc을 마크다운(Markdown)으로 작성할 수 있게 됐다는 점이에요. JavaDoc은 코드 문서화의 핵심인데, 지금까지는 HTML 태그나 `@`로 시작하는 특수 태그를 섞어서 써야 했어요. 솔직히 말하면, 이 방식이 익숙해지기 전까지는 꽤 번거롭다는 느낌을 지울 수 없었죠. 예를 들어, 간단한 설명을 쓰더라도:

/**
 * <p>This is a method that does something.</p>
 * @param input the input value
 * @return the result
 */

이런 식으로 HTML 문법을 신경 써야 했는데, 이제는 마크다운으로 훨씬 간단하게 작성할 수 있습니다. 예를 들면:

/**
 * # My Useful Method
 * This method does **something cool** with the input.
 * 
 * - *Parameter*: `input` - 입력값
 * - *Returns*: 계산된 결과
 */

어때요? 훨씬 직관적이고 쓰기 편하지 않나요? 마크다운은 이미 개발자들 사이에서 문서 작성이나 README 파일 만들 때 많이 쓰이니까, JavaDoc에 적용된 것도 자연스럽게 다가옵니다. 특히 강조나 리스트 같은 형식을 넣을 때 HTML보다 훨씬 덜 부담스러워요.

이 변화의 장점은 단순히 편리함을 넘어 문서의 가독성까지 높아진다는 점이에요. 팀으로 프로젝트를 진행하다 보면 코드만큼 문서도 중요하잖아요. 깔끔하게 정리된 JavaDoc은 동료 개발자나 나중에 코드를 다시 볼 때 큰 도움이 됩니다. 다만, 기존 HTML 기반 JavaDoc과 호환성을 유지해야 하니까, 마크다운이 완전히 자리를 잡으려면 시간이 좀 걸릴 수도 있어요. 그래도 이 방향성은 정말 마음에 드네요. 혹시 문서 작업을 자주 하는 분들이라면 JDK 23에서 한 번 써보고 감상을 남겨보는 것도 좋을 것 같아요.


4. 생성자 유연성 강화: Flexible Constructor Bodies

이번엔 생성자 관련해서 흥미로운 업데이트를 가져왔어요. JDK 23에서 `Flexible Constructor Bodies`라는 기능이 두 번째 미리보기(Second Preview)로 등장했는데, 이건 생성자 내부에서 `super()` 호출 전에 더 많은 일을 할 수 있게 해줍니다. 원래 Java에서는 생성자가 호출될 때 부모 클래스의 생성자를 먼저 호출해야 했고, 그 전에 필드 초기화나 로직을 넣는 게 제한적이었어요. 예를 들어:

public class MyClass extends ParentClass {
    private int value;

    public MyClass(int value) {
        super(); // 무조건 먼저 호출해야 함
        if (value < 0) {
            throw new IllegalArgumentException("음수는 안 됩니다!");
        }
        this.value = value;
    }
}

이런 코드에서는 `super()`가 맨 위에 있어야 해서, 입력값 검증이나 초기화 로직을 그 아래로 배치할 수밖에 없었죠. 하지만 JDK 23에서는 이 순서를 좀 더 유연하게 바꿀 수 있어요:

public class MyClass extends ParentClass {
    private int value;

    public MyClass(int value) {
        if (value < 0) {
            throw new IllegalArgumentException("음수는 안 됩니다!");
        }
        this.value = value;
        super(); // 이제 이렇게 뒤로 보낼 수 있음
    }
}

이 차이가 별거 아닌 것처럼 보일 수도 있는데, 실제로 객체 생성 로직이 복잡해질수록 유용함이 드러납니다. 예를 들어, 입력값에 따라 초기화 방식을 다르게 하고 싶거나, 특정 조건을 먼저 체크한 뒤에 부모 클래스 생성자를 호출해야 할 때 훨씬 자유롭게 코드를 짤 수 있어요.

이 기능의 또 다른 장점은 코드의 의도를 더 명확하게 드러낼 수 있다는 점이에요. 생성자에서 객체의 상태를 설정하는 로직을 앞쪽에 배치하면, 읽는 사람이 "이 객체가 어떤 조건에서 만들어지는지"를 바로 파악할 수 있죠. 다만, 이건 미리보기 기능이라 아직 정식 스펙으로 확정된 건 아니에요. 그래서 실험적인 프로젝트에서 써보고, 예상치 못한 문제가 생길 수도 있으니 주의 깊게 테스트해보는 게 좋겠습니다.


5. Z Garbage Collector의 기본 모드 변경

JDK 23에서는 성능 관련해서도 의미 있는 변화가 있었어요. 바로 Z Garbage Collector(ZGC)의 기본 모드가 generational로 바뀐 건데, 이게 뭔지 하나씩 풀어볼게요. ZGC는 원래 JDK 15에서 처음 정식으로 도입된 가비지 컬렉터인데, 짧은 GC 멈춤 시간과 대규모 힙을 효율적으로 관리하는 걸 목표로 설계됐어요. 이번에 generational 모드가 기본값으로 바뀌면서, 메모리 관리 방식이 한 단계 더 진화한 느낌입니다.

generational이라는 건 객체를 생성 시점에 따라 '젊은 객체(Young Generation)'와 '오래된 객체(Old Generation)'로 나누어서 관리한다는 뜻이에요. 일반적으로 프로그램에서 객체는 생성된 직후에 자주 사용되다가 시간이 지나면 점점 덜 쓰이거나 수명이 끝나는 경우가 많죠. 이걸 활용해서 젊은 객체는 자주 수집하고, 오래된 객체는 덜 자주 정리하는 방식으로 효율성을 높이는 겁니다.

이전에는 ZGC를 사용할 때 generational 모드를 따로 활성화해야 했어요. 예를 들어, JVM 옵션에서 `-XX:+UseZGC -XX:+ZGenerational` 같은 플래그를 추가해야 했는데, 이제는 그냥 `-XX:+UseZGC`만 쓰면 기본으로 generational 모드가 적용됩니다. 이렇게 설정을 간소화한 덕분에 사용자 입장에서는 별다른 고민 없이 최적화된 GC를 쓸 수 있게 됐어요.

어떤 점이 좋아졌냐면, 메모리 사용량과 GC 성능이 더 균형 있게 개선됐다는 점이에요. 특히 메모리 사용량이 많은 애플리케이션이나, 실시간 응답 속도가 중요한 시스템에서 효과를 볼 가능성이 큽니다. 예를 들어, 웹 서버나 데이터 처리 애플리케이션에서 짧은 멈춤 시간과 안정적인 성능을 유지하는 데 도움이 될 거예요. 다만, 애플리케이션 특성에 따라 기존의 non-generational 모드가 더 나을 수도 있으니, 실제로 돌려보고 비교해보는 게 좋겠죠. 만약 이전 모드로 돌리고 싶다면 `-XX:-ZGenerational` 옵션을 추가하면 됩니다.


6. 기타 개선 사항과 미리보기 기능들

ZGC 외에도 JDK 23에는 여러 작은 개선들이 포함돼 있어요. 예를 들어, `Vector API`가 여섯 번째 인큐베이터(Incubator) 단계로 업데이트됐는데, 이건 CPU의 벡터 연산을 활용해서 병렬 처리를 최적화하는 기능이에요. 주로 머신러닝이나 과학 계산 같은 분야에서 빛을 발할 수 있는데, 아직 완성 단계는 아니라서 실험적인 용도로 써볼 만합니다.

또 하나 언급할 만한 건 `Structured Concurrency`가 두 번째 미리보기 단계로 들어갔다는 점이에요. 이건 멀티스레드 프로그래밍을 더 구조화된 방식으로 다룰 수 있게 해주는 기능인데, 비동기 작업을 깔끔하게 관리하고 싶을 때 유용해요. 예를 들어:

try (var scope = new StructuredTaskScope.ShutdownOnFailure()) {
    Future<String> task1 = scope.fork(() -> processData1());
    Future<String> task2 = scope.fork(() -> processData2());
    scope.join();
    return task1.resultNow() + " " + task2.resultNow();
}

이렇게 하면 작업을 그룹으로 묶어서 실패나 성공 여부를 한 번에 관리할 수 있어요. 기존의 스레드 풀이나 CompletableFuture를 쓸 때보다 코드가 좀 더 명확해지는 장점이 있죠. 아직 미리보기 단계라 실무에 바로 적용하기보다는 테스트 삼아 써보는 걸 추천합니다.


JDK 23, 어떤 의미가 있을까?

지금까지 JDK 23의 주요 기능들을 쭉 살펴봤는데요, 이번 버전을 한마디로 정리하자면 '개발 경험과 성능의 균형'을 추구한 업데이트라는 생각이 들어요. 기본형 패턴 매칭이나 모듈 가져오기 같은 기능은 코드를 더 간결하고 유연하게 만들어주고, 마크다운 JavaDoc이나 생성자 유연성은 문서화와 설계 편의성을 높여줍니다. 여기에 ZGC 개선까지 더해지면서 성능 면에서도 한 발짝 앞으로 나간 느낌이에요.

다만, JDK 23은 LTS(Long Term Support) 버전이 아니라는 점을 잊지 말아야 해요. 6개월간 지원받다가 다음 버전인 JDK 24로 넘어가게 되니까, 안정성이 중요한 프로덕션 환경이라면 JDK 21 같은 LTS 버전을 유지하는 게 나을 수도 있습니다. 반면, 최신 기능을 미리 써보고 싶거나 다음 LTS인 JDK 25를 준비하려는 분들에게는 좋은 실험 무대가 될 거예요.

개인적으로 이번 업데이트에서 가장 마음에 드는 건 마크다운 JavaDoc과 ZGC 기본 모드 변경이에요. 문서 작업이 편해지는 것도 좋고, 성능 개선을 따로 설정 없이 누릴 수 있다는 점도 매력적이네요. 여러분이 JDK 23에서 주목하는 부분이 있다면 어떤 건지 궁금하네요. 새로운 기능을 직접 써보고 나서 느낀 점이나 개선 아이디어가 있다면 공유해보는 것도 재밌을 것 같아요.

Comments