선택 심화: 프리뷰 기능 사용법과 소스 분리
JDK 25의 프리뷰 기능이 무엇인지, 컴파일과 실행에 --enable-preview를 어떻게 붙이는지, 프리뷰 코드를 왜 정식 코드와 따로 빌드하는지 배웁니다.
이 장은 선택 심화 과정입니다. 36장까지의 기본 과정에서 문법과 객체지향, 컬렉션, 예외, 파일, 네트워크, 동시성, JVM 실행 원리를 익힌 뒤 관심이 생겼을 때 읽으면 됩니다. 38장을 포함해 다른 장은 이 장의 내용을 안다고 가정하지 않으므로, 건너뛰어도 학습 흐름이 끊기지 않습니다.
JDK 25의 API 문서를 보면 java.lang 패키지에 StableValue라는 클래스가 있습니다. 그런데 이 클래스를 쓰는 코드를 평소처럼 javac로 컴파일하면 오류가 납니다. JDK 안에 분명히 들어 있는 클래스인데도 그냥은 쓸 수 없는 이유는 이것이 프리뷰(preview) 기능이기 때문입니다. 이 글에서는 프리뷰 기능이 무엇인지, 어떻게 켜고 실행하는지, 그리고 프리뷰 코드를 왜 정식 코드와 따로 빌드해야 하는지를 차례로 살펴봅니다.
프리뷰 기능이란
프리뷰 기능은 설계와 구현은 끝났지만 아직 Java SE의 영구 기능으로 확정되지 않은 언어 기능이나 API입니다. JDK 개발진은 새 기능을 곧바로 확정하지 않고, 한 릴리스 이상 프리뷰로 내보내 실제로 써 본 개발자들의 의견을 받습니다. 그 의견에 따라 다음 릴리스에서 API가 바뀌기도 하고, 그대로 정식 기능이 되기도 하며, 아예 빠지기도 합니다. 이 절차는 JEP 12(Preview Features)에 정해져 있습니다.
바뀔 수 있는 기능이 모르는 사이에 코드에 섞이면 곤란하므로, 프리뷰 기능은 기본으로 꺼져 있습니다. 개발자가 --enable-preview 옵션을 붙여 '바뀔 수 있다는 것을 알고 쓴다'고 밝혀야만 컴파일러와 JVM이 허용합니다. JDK 25에서 이 장과 관련된 기능의 상태는 다음과 같습니다.
| 기능 | 대표 API·문법 | JDK 25에서의 상태 |
|---|---|---|
| Stable Values (JEP 502) | StableValue | 프리뷰(첫 번째) |
| 구조화된 동시성 (JEP 505) | StructuredTaskScope | 프리뷰(다섯 번째) |
| 기본형 패턴 (JEP 507) | instanceof byte b, long 값의 switch | 프리뷰(세 번째) |
| PEM 인코딩 (JEP 470) | 암호 키·인증서를 PEM 텍스트와 서로 변환하는 API | 프리뷰(첫 번째) |
| Scoped Values (JEP 506) | ScopedValue | 정식 |
| 모듈 import 선언 (JEP 511) | import module java.base; | 정식 |
| 간결한 소스 파일과 인스턴스 main 메서드 (JEP 512) | void main() | 정식 |
| 유연한 생성자 본문 (JEP 513) | super(...) 앞에 문장 쓰기 | 정식 |
표의 위쪽 네 기능은 JDK 25에서도 프리뷰이고, 아래쪽 네 기능은 앞선 릴리스들에서 프리뷰를 거친 끝에 JDK 25에서 정식 기능이 되었습니다. 예를 들어 ScopedValue는 JDK 21부터 24까지 네 번 프리뷰를 거쳤고, 정식이 된 지금은 가상 스레드와 ScopedValue에서처럼 아무 옵션 없이 씁니다. 같은 JDK 25에 들어 있는 Vector API(JEP 508)는 프리뷰가 아니라 인큐베이터(incubator) 모듈로 제공되므로, --enable-preview가 아니라 --add-modules jdk.incubator.vector로 켠다는 점도 구분해 두세요.
옵션 없이 컴파일하면 생기는 일
먼저 프리뷰를 켜지 않고 StableValue를 써 보겠습니다. 다음 프로그램은 비어 있는 StableValue를 만들고 orElseSet으로 값을 채운 뒤 출력합니다. 두 메서드가 하는 일은 다음 절에서 자세히 봅니다.
public final class PreviewFeatureWithoutFlagFailure {
public static void main(String[] args) {
StableValue<String> value = StableValue.of();
System.out.println(value.orElseSet(() -> "preview"));
}
}JDK 25의 javac로 프리뷰 옵션 없이 컴파일합니다. 컴파일 단계에서 멈추므로 java 명령까지 갈 일은 없습니다.
javac --release 25 PreviewFeatureWithoutFlagFailure.java컴파일은 실패합니다. 실제 출력에는 파일 이름과 줄 번호, 문제 위치를 가리키는 표시가 함께 나오지만, 메시지의 핵심은 다음 두 줄입니다.
error: StableValue is a preview API and is disabled by default.
(use --enable-preview to enable preview APIs)메시지 그대로, StableValue는 프리뷰 API이고 기본으로 꺼져 있으니 --enable-preview로 켜라는 뜻입니다. 여기서 알 수 있듯이 클래스가 JDK에 들어 있다는 사실만으로 정식 API라고 판단하면 안 됩니다. API 문서에서도 프리뷰 API에는 PREVIEW 표시와 함께 'StableValue is a preview API of the Java platform' 같은 안내가 붙어 있으니, 처음 보는 클래스라면 이 표시부터 확인하는 습관을 들이면 좋습니다.
컴파일할 때와 실행할 때 모두 켜야 하는 이유
이번에는 프리뷰를 켜고 끝까지 실행해 보겠습니다. 그 전에 StableValue가 무엇인지 짧게 짚고 가겠습니다. StableValue는 값을 딱 한 번만 설정할 수 있는 칸입니다. StableValue.of()는 비어 있는 칸을 만들고, orElseSet(공급자)는 칸이 비어 있으면 공급자를 호출해 그 결과로 칸을 채운 뒤 돌려줍니다. 칸이 이미 채워져 있으면 공급자를 부르지 않고 저장된 값을 돌려주므로, 처음 쓸 때 한 번만 계산하는 지연 초기화(lazy initialization)에 쓸 수 있습니다.
아래 프로그램은 orElseSet을 두 번 부르면서, 공급자가 실제로 몇 번 호출되는지 AtomicInteger로 셉니다.
import java.util.concurrent.atomic.AtomicInteger;
public final class StableValuePreviewProbe {
public static void main(String[] args) {
StableValue<String> value = StableValue.of();
AtomicInteger calls = new AtomicInteger();
String first = value.orElseSet(() -> "value-" + calls.incrementAndGet());
String second = value.orElseSet(() -> "value-" + calls.incrementAndGet());
System.out.println("first=" + first);
System.out.println("second=" + second);
System.out.println("initializer-calls=" + calls.get());
}
}컴파일과 실행 명령은 다음과 같습니다. 두 명령 모두에 --enable-preview가 들어 있다는 점을 눈여겨보세요.
javac --enable-preview --release 25 -Xlint:preview StableValuePreviewProbe.java
java --enable-preview StableValuePreviewProbe이 예제는 JDK 25에서만 컴파일됩니다. StableValue의 명세대로라면 출력은 다음과 같습니다.
first=value-1
second=value-1
initializer-calls=1첫 번째 orElseSet에서는 칸이 비어 있으므로 공급자가 호출되고, 카운터가 1이 되면서 value-1이 칸에 저장됩니다. 두 번째 호출에서는 칸이 이미 차 있으므로 공급자를 아예 부르지 않고 저장된 value-1을 돌려줍니다. 그래서 두 번째 줄도 value-1이고, 공급자 호출 횟수는 1에 머뭅니다.
API 문서는 여기에 두 가지를 덧붙입니다. 첫째, 공급자가 예외를 던지면 그 예외가 호출한 쪽으로 그대로 전달되고 칸은 비어 있는 채로 남습니다. 그래서 다음 호출에서 초기화를 다시 시도할 수 있습니다. 둘째, 여러 스레드가 동시에 orElseSet을 호출해도 공급자가 정상적으로 끝나는 한 공급자는 한 번만 호출됩니다.
명령에 들어간 옵션 하나씩 보기
javac의 --enable-preview는 소스에서 프리뷰 문법과 API를 쓰도록 허용합니다. 이 옵션은 반드시 --release(또는 --source)와 함께, 그것도 지금 사용하는 JDK의 버전 번호로 지정해야 합니다. 프리뷰 기능은 그 릴리스에만 들어 있는 시험판이라서, JDK 25의 컴파일러는 JDK 25의 프리뷰만 알고 이전 릴리스의 프리뷰 규칙은 알지 못하기 때문입니다. 버전을 빼면 --enable-preview must be used with either -source or --release, 다른 번호를 주면 preview language features are only supported for release 25라는 오류가 나고 컴파일이 시작되지 않습니다.
--release와 --source 중에서는 --release를 권합니다. --release 25는 소스 문법 수준과 클래스 파일 버전, 사용할 수 있는 플랫폼 API를 한꺼번에 Java 25에 맞춰 주기 때문입니다.
-Xlint:preview는 프리뷰 기능을 쓴 위치마다 경고를 출력하게 합니다. 이 옵션이 없으면 javac는 컴파일에 성공하면서 다음처럼 파일 단위의 안내 두 줄만 출력합니다.
Note: StableValuePreviewProbe.java uses preview features of Java SE 25.
Note: Recompile with -Xlint:preview for details.-Xlint:preview를 붙이면 warning: [preview] StableValue is a preview API and may be removed in a future release.처럼 사용 위치마다 경고가 나옵니다. 경고가 있어도 컴파일은 성공합니다. 그래도 이 경고를 끄지 말고 빌드 로그에 남겨 두면, 프리뷰를 쓰는 곳이 늘어날 때 리뷰에서 바로 눈에 띕니다.
마지막으로 java의 --enable-preview는 프리뷰 기능에 의존하는 클래스를 JVM이 불러오도록 허용합니다. 컴파일할 때 이미 켰는데 왜 또 켜야 할까요? javac와 java는 서로 다른 프로그램이고, 컴파일할 때 고른 옵션이 실행할 때로 자동으로 넘어가지 않기 때문입니다. 대신 javac는 프리뷰 기능을 쓴 클래스 파일에 'JDK 25의 프리뷰 기능에 의존한다'는 표식을 남기고, JVM은 클래스를 불러올 때 이 표식을 검사합니다. 실행할 때 옵션을 빠뜨리면 다음과 같이 클래스를 불러오는 단계에서 실행이 거부됩니다.
Error: LinkageError occurred while loading main class StableValuePreviewProbe
java.lang.UnsupportedClassVersionError: Preview features are not enabled for StableValuePreviewProbe (class file version 69.65535). Try running with '--enable-preview'괄호 안의 69.65535가 바로 그 표식입니다. 69는 Java 25 클래스 파일의 메이저 버전(major version)이고, 마이너 버전(minor version) 65535는 프리뷰 기능에 의존한다는 뜻입니다. 클래스 파일의 헤더를 직접 읽어 이 값을 확인하는 방법은 프리뷰 클래스 파일과 실행 규칙에서 다룹니다.
짧게 실험할 때는 소스 파일 모드로 컴파일과 실행을 한 번에 할 수도 있습니다. java --enable-preview StableValuePreviewProbe.java처럼 클래스 이름 대신 .java 파일을 주면 됩니다. JDK 25의 java 명령 문서에 따르면, 이 모드에서 --enable-preview를 주면 --source를 생략해도 실행 중인 JDK의 버전에 맞춰 --release 25가 적용됩니다. JDK 21에서는 같은 명령에 --source 21을 함께 적어야 했으니, 오래된 자료의 명령을 볼 때 이 차이를 기억해 두세요.
더 깊이: 프리뷰 클래스 파일은 컴파일한 릴리스에서만 실행된다
JVM 명세(JVMS 4.1)는 Java SE N을 구현한 JVM이 프리뷰 표식이 있는 클래스 파일을 불러오는 조건을 정해 둡니다. 메이저 버전이 N과 같고 프리뷰가 켜져 있을 때만 불러오며, 다른 릴리스의 프리뷰 클래스 파일은 옵션과 상관없이 거부합니다. 그래서 JDK 25에서 프리뷰 기능으로 만든 JAR는 JDK 26에서 --enable-preview를 붙여도 실행되지 않고, 새 JDK로 다시 컴파일해야 합니다. 다시 컴파일하는 것만으로 끝나지 않을 수도 있습니다. 실제로 StableValue는 JDK 26의 두 번째 프리뷰(JEP 526)에서 LazyConstant로 이름이 바뀌고 orElseSet 같은 메서드도 빠져서, 이 글의 예제를 그대로 옮길 수 없습니다.
한편 --enable-preview로 컴파일했다고 모든 클래스에 표식이 붙는 것은 아닙니다. javac는 프리뷰 문법이나 API를 실제로 사용한 소스 파일을 기억해 두었다가, 그 파일에서 나온 클래스 파일에만 마이너 버전 65535를 기록하고 나머지 소스 파일의 클래스는 평범한 클래스 파일로 만듭니다. 그래서 같은 파일에 함께 선언한 클래스는 프리뷰 기능을 직접 쓰지 않았어도 같은 표식을 받습니다. 다만 표식이 없는 클래스라도 프리뷰 클래스를 불러 쓰면, 그 프리뷰 클래스가 로드되는 순간 앞과 같은 오류가 납니다. 다음 절에서 소스를 아예 나누는 이유가 여기에 있습니다.
프리뷰 언어 기능도 같은 옵션으로 켠다
지금까지는 프리뷰 API를 봤지만, 프리뷰 언어 기능도 똑같은 옵션을 씁니다. JDK 25의 기본형 패턴(JEP 507)을 쓰면 switch로 long 값을 고르고, case long number처럼 기본형 타입 패턴을 쓸 수 있습니다. 다음 소스를 옵션 없이 컴파일하면 프리뷰 언어 기능이 꺼져 있다는 오류가 나고, 프리뷰를 켜야 컴파일됩니다.
public final class PrimitivePatternPreviewProbe {
static String classify(long value) {
return switch (value) {
case 0L -> "zero";
case long number when number > 0 -> "positive:" + number;
case long number -> "negative:" + number;
};
}
public static void main(String[] args) {
System.out.println(classify(0L));
System.out.println(classify(25L));
System.out.println(classify(-7L));
}
}javac --enable-preview --release 25 -Xlint:preview PrimitivePatternPreviewProbe.java
java --enable-preview PrimitivePatternPreviewProbe-Xlint:preview를 주면 컴파일이 성공해도 프리뷰 기능을 쓴 곳마다 경고가 함께 나옵니다. 이 예제도 JDK 25에서만 컴파일되며, 명세대로라면 출력은 다음과 같습니다.
zero
positive:25
negative:-7case 0L은 0과 같은 값을 고르고, case long number when number > 0은 양수만 받습니다. 마지막 case long number는 남은 모든 long 값을 받기 때문에 default 없이도 switch가 모든 경우를 다룹니다. 기본형 패턴의 자세한 규칙은 기본형 패턴과 switch에서 다룹니다.
프리뷰 코드를 정식 코드와 나눠 빌드하기
앞에서 본 것처럼 프리뷰 클래스가 하나라도 섞인 애플리케이션은 실행할 때마다 --enable-preview가 필요하고, 그 클래스를 컴파일한 JDK 25에서만 돌아갑니다. 실험용으로 넣은 클래스 하나 때문에 운영 중인 애플리케이션 전체가 특정 JDK 버전과 프리뷰 옵션에 묶이는 셈입니다. 이를 피하는 가장 확실한 방법은 프리뷰 코드를 소스 디렉터리부터 따로 두고, 컴파일 결과물도 다른 디렉터리에 만드는 것입니다.
예를 들어 정식 소스는 src/main/java, 프리뷰 실험은 src/preview/java에 두고, 컴파일 결과도 build/classes/main과 build/classes/preview로 나눕니다. 그런 다음 정식 결과물을 JAR로 묶는 작업이 build/classes/preview를 참조하지 않는지 확인합니다. 의존 방향도 한쪽으로만 둡니다. 프리뷰 실험 코드가 정식 코드를 쓰는 것은 괜찮지만, 정식 코드가 프리뷰 코드를 쓰기 시작하면 다시 섞이게 됩니다.
Gradle이나 Maven을 쓴다면 별도의 소스 세트나 모듈을 만들어 같은 구조를 적용할 수 있습니다. 다음 프로그램은 두 소스 세트에 각각 어떤 javac 인수를 줘야 하는지 목록으로 만들어 보여 줍니다. 컴파일러를 실제로 실행하지는 않고 프리뷰 기능도 쓰지 않으므로, 옵션 없이 컴파일하고 실행할 수 있습니다.
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
public final class PreviewPathPolicy {
private record Compilation(Path source, Path output, boolean preview) {}
static List<String> javacArguments(Compilation compilation) {
List<String> arguments = new ArrayList<>();
if (compilation.preview()) {
arguments.add("--enable-preview");
}
arguments.add("--release");
arguments.add("25");
if (compilation.preview()) {
arguments.add("-Xlint:preview");
}
arguments.add("-d");
arguments.add(compilation.output().toString());
arguments.add(compilation.source().toString());
return List.copyOf(arguments);
}
public static void main(String[] args) {
Compilation stable =
new Compilation(
Path.of("src/main/java/App.java"), Path.of("build/classes/main"), false);
Compilation preview =
new Compilation(
Path.of("src/preview/java/Lab.java"),
Path.of("build/classes/preview"),
true);
System.out.println(javacArguments(stable));
System.out.println(javacArguments(preview));
}
}List의 toString()은 원소를 쉼표로 이어 대괄호로 감쌉니다. 그래서 각 줄은 javac에 넘길 인수를 넘길 순서대로 보여 줍니다.
[--release, 25, -d, build/classes/main, src/main/java/App.java]
[--enable-preview, --release, 25, -Xlint:preview, -d, build/classes/preview, src/preview/java/Lab.java]첫 줄은 정식 소스용 인수입니다. 프리뷰 관련 옵션 없이 --release 25만 주고, 결과는 -d로 지정한 build/classes/main에 씁니다. 둘째 줄은 프리뷰 소스용으로, --enable-preview와 -Xlint:preview가 더해지고 출력 디렉터리도 build/classes/preview로 바뀝니다. 정식 소스는 프리뷰를 켜지 않고 컴파일하므로 프리뷰 기능을 쓰면 컴파일 단계에서 막히고, 그 결과 정식 결과물에는 마이너 버전 65535인 클래스가 들어갈 수 없습니다.
프리뷰 기능은 어디에 쓸까
프리뷰 기능은 운영 요구 사항을 해결하는 도구라기보다 다음 버전의 Java를 미리 배우고 평가하는 수단입니다. 실험하기 전에 기능마다 무엇을 확인할지 정해 두면 시간을 아낄 수 있습니다. 이 장에서 다루는 기능이라면 다음 질문에서 출발하면 됩니다.
- Stable Values: 초기화가 정말 한 번만 일어나는지, 여러 스레드가 동시에 접근하면 어떻게 되는지
- 구조화된 동시성: 하위 작업의 수명이 코드 블록 안으로 제한되는지, 하나가 실패하면 나머지가 어떻게 취소되는지
- 기본형 패턴: 정보 손실 없는 변환 검사가 기존의 범위 검사와 형변환보다 읽기 쉬운지
실험 결과를 정리할 때는 기능의 JEP 번호와 프리뷰 차수, 사용한 JDK 버전을 함께 적어 두세요. 같은 이름의 기능이라도 다음 프리뷰에서 API가 달라질 수 있기 때문입니다. 공식 기준은 JDK 25 기능 목록과 각 JEP 문서입니다.
블로그 글의 예제는 이전 프리뷰의 API로 쓰인 경우가 많습니다. 생성자나 메서드 시그니처를 그대로 베끼지 말고 JDK 25 API 문서와 대조해 보세요. 예를 들어 StructuredTaskScope는 JDK 25에서 범위를 만드는 방법 자체가 바뀌어서, JDK 24까지의 예제는 JDK 25에서 컴파일되지 않습니다.
JDK 25가 LTS라면 그 안의 프리뷰 기능도 오래 안정적으로 쓸 수 있나요?
아닙니다. LTS(장기 지원)는 JDK 공급자가 해당 릴리스에 보안 패치와 버그 수정을 오래 제공하겠다는 지원 정책일 뿐, 프리뷰 기능의 명세 상태를 바꾸지 않습니다. JDK 25를 오래 쓰더라도 그 안의 프리뷰 API는 다음 릴리스에서 바뀔 수 있고, 앞에서 본 것처럼 JDK 25의 프리뷰 클래스 파일은 다른 릴리스에서 실행되지도 않습니다. 운영 코드에는 정식 기능만 쓰고, 프리뷰는 분리된 실험으로 유지하세요.
정리
- 프리뷰 기능은 완성됐지만 아직 확정되지 않은 기능입니다. 다음 릴리스에서 바뀌거나 빠질 수 있어서 기본으로 꺼져 있습니다.
- 컴파일할 때는
javac --enable-preview --release 25, 실행할 때는java --enable-preview가 필요합니다. 두 프로그램은 서로의 옵션을 알지 못합니다. - 프리뷰 기능을 쓴 클래스 파일에는 마이너 버전 65535가 기록되고, 같은 릴리스의 JVM에서 프리뷰를 켰을 때만 실행됩니다.
- 프리뷰 실험은 소스 디렉터리와 결과물 디렉터리를 정식 코드와 나눠, 정식 결과물에 섞이지 않게 합니다.
연습 문제
프리뷰 여부에 따라 컴파일 명령과 실행 명령의 인수 목록을 만드는 두 메서드 compile(String file, boolean preview)와 run(String className, boolean preview)를 작성하세요. 정식 소스는 --release 25만 붙여 컴파일하고, 프리뷰 소스는 --enable-preview, --release 25, -Xlint:preview를 붙여 컴파일합니다. 프리뷰 클래스를 실행할 때는 --enable-preview를 붙입니다.
입력은 프리뷰 소스 StableLab.java(클래스 StableLab)와 정식 소스 Application.java(클래스 Application) 두 가지입니다. 코드를 쓰기 전에 네 목록이 어떻게 나와야 하는지 먼저 적어 보세요. 이 메서드들은 명령을 실행하지 않고 목록만 만든다는 점도 기억해 두세요. 목록이 맞게 나왔다고 해서 JDK 25에서 컴파일과 실행이 성공한다는 뜻은 아닙니다.
해설 보기
import java.util.List;
public final class PreviewCommandPolicySolution {
static List<String> compile(String file, boolean preview) {
if (preview) {
return List.of("javac", "--enable-preview", "--release", "25", "-Xlint:preview", file);
}
return List.of("javac", "--release", "25", file);
}
static List<String> run(String className, boolean preview) {
if (preview) {
return List.of("java", "--enable-preview", className);
}
return List.of("java", className);
}
public static void main(String[] args) {
System.out.println(compile("StableLab.java", true));
System.out.println(run("StableLab", true));
System.out.println(compile("Application.java", false));
System.out.println(run("Application", false));
}
}실행 결과:
[javac, --enable-preview, --release, 25, -Xlint:preview, StableLab.java]
[java, --enable-preview, StableLab]
[javac, --release, 25, Application.java]
[java, Application]안정 코드와 프리뷰 코드의 명령 경계
Java 25 · 컴파일과 실행의 프리뷰 허용
안정 코드와 프리뷰 코드의 명령 경계
컴파일과 실행에 필요한 옵션이 다릅니다. 본문의 프로그램은 명령 인수 목록만 만들며, 컴파일러나 Java 프로세스를 실행하지 않습니다.
| 만들 명령 | 필요한 옵션 | 풀이가 하지 않는 일 |
|---|---|---|
| 안정 소스 컴파일 | --release 25 | 프로세스를 실행하거나 컴파일 결과를 확인하지 않음 |
| 프리뷰 소스 컴파일 | --enable-preview --release 25 -Xlint:preview | 실행 JDK의 버전이나 산출물 격리를 검사하지 않음 |
| 프리뷰 클래스 실행 | --enable-preview | 메인 클래스 존재·의존성·JDK 25 일치를 검사하지 않음 |
- 안정 소스 컴파일
- 필요한 옵션
--release 25- 풀이가 하지 않는 일
- 프로세스를 실행하거나 컴파일 결과를 확인하지 않음
- 프리뷰 소스 컴파일
- 필요한 옵션
--enable-preview--release 25-Xlint:preview- 풀이가 하지 않는 일
- 실행 JDK의 버전이나 산출물 격리를 검사하지 않음
- 프리뷰 클래스 실행
- 필요한 옵션
--enable-preview- 풀이가 하지 않는 일
- 메인 클래스 존재·의존성·JDK 25 일치를 검사하지 않음
PreviewCommandPolicySolution은 인수 목록을 만드는 예제입니다. Java 25 프리뷰 클래스는 Java 25 런타임과 프리뷰 허용이 필요하며, 실제 컴파일·실행 성공을 이 목록만으로 판정할 수는 없습니다.
프리뷰 소스의 컴파일 목록에는 세 옵션이, 실행 목록에는 --enable-preview 하나가 들어갑니다. 정식 소스의 실행 목록은 java와 클래스 이름뿐입니다. --release는 컴파일 대상을 정하는 javac 옵션이므로 실행 목록에 옮겨 적지 않습니다. 앞에서 본 디렉터리 분리와 합치려면 컴파일 목록에는 -d와 출력 디렉터리를, 실행 목록에는 그 디렉터리를 가리키는 -cp를 더해야 합니다.
다음 글
구조화된 동시성과 StructuredTaskScope에서는 JDK 25의 프리뷰 API인 StructuredTaskScope로 여러 하위 작업을 하나의 코드 블록 안에서 시작하고 함께 기다리는 방법을 봅니다. 예제는 모두 이 글에서 익힌 프리뷰 옵션으로 컴파일하고 실행합니다.