2023.09.19에 LTS 버전인 Java 21이 출시될 예정이다. Preview를 포함해서 총 15개의 feature가 포함된다.
이번 시간에는 정식 릴리즈된 feature들을 살펴보고, 다음 시간엔 Preview를 살펴보도록 하자.
Virtual Threads
Virtual Threads는 높은 처리량의 동시 애플리케이션을 작성, 유지 관리, 모니터링하는데 드는 비용을 획기적으로 줄여주는 경량 스레드이다. Thread Per Reuqest 스타일로 작성된 서버 애플리케이션을 최적의 하드웨어로 확장할 수 있다. 또한 `java.lang.Thread API`를 사용하는 기존 코드를 최소한의 변경으로 가상 스레드를 사용할 수 있게 하위호환성을 잘 유지한다.
예제
// 기본 Thread
new Thread(() -> System.out.println("Hello World")).start();
// Virtual Thread
Thread.startVirtualThread(() -> System.out.println("Hello World")).start();
위처럼 간단하게 Virtual Thread를 사용할 수 있다.
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close() is called implicitly, and waits
위 코드는 Virtual Thread를 통해서 10,000 번 1초 sleep을 하는 코드이다. 기존의 Thread와는 다르게 소수의 OS Thread를 사용해서 처리할 수 있다. JEP에서는 아마 1개의 OS Thread만을 사용해서 처리할 수 있을 거라고 한다. 만약 Executors.newCachedThreadPool()`를 사용했다면 10,000개의 OS Thread를 생성하려 시도하면서 문제가 생겼을 수도 있다.
Virtual Thread는 일반적인 서버 애플리케이션의 처리량을 개선하는데 도움이 된다. 왜냐하면 대부분의 어플리케이션이 DB, API, Network 호출처럼 Block 된 채 기다리고 있는 작업이 많기 때문이다.
Pattern Matching for switch
switch 문 및 expression에 대한 Pattern Matching으로 Java 프로그래밍 언어를 개선한다. Pattern Matching을 switch로 확장하면 각각 특정 동작이 있는 여러 패턴에 대해 expression을 테스트할 수 있으므로 복잡한 데이터 지향 쿼리를 간결하고 안전하게 표현할 수 있다. case label에 패턴 매칭을 사용하여 switch 문 및 statement의 표현력과 적용성을 확장할 수 있다. 그리고 기존과 다른 점은 case 문에 null을 허용한다.
예제
// Prior to Java 16
if (obj instanceof String) {
String s = (String)obj;
... use s ...
}
// As of Java 16
if (obj instanceof String s) {
... use s ...
}
위는 Java 16 전후의 feature이며, Pattern matching 하여 s를 변수로 곧바로 사용할 수 있다.
// Prior to Java 21
static String formatter(Object obj) {
String formatted = "unknown";
if (obj instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (obj instanceof Long l) {
formatted = String.format("long %d", l);
} else if (obj instanceof Double d) {
formatted = String.format("double %f", d);
} else if (obj instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
// As of Java 21
static String formatterPatternSwitch(Object obj) {
return switch (obj) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> obj.toString();
};
}
Pattern matching + switch 문을 활용하여 코드를 간결하고 안전하게 표현한다.
// Prior to Java 21
static void testFooBarOld(String s) {
if (s == null) {
System.out.println("Oops!");
return;
}
switch (s) {
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
// As of Java 21
static void testFooBarNew(String s) {
switch (s) {
case null -> System.out.println("Oops");
case "Foo", "Bar" -> System.out.println("Great");
default -> System.out.println("Ok");
}
}
null을 별도로 처리하지않고, case를 통해 활용할 수 있다.
// As of Java 21
static void testStringOld(String response) {
switch (response) {
case null -> { }
case String s -> {
if (s.equalsIgnoreCase("YES"))
System.out.println("You got it");
else if (s.equalsIgnoreCase("NO"))
System.out.println("Shame");
else
System.out.println("Sorry?");
}
}
}
// As of Java 21
static void testStringNew(String response) {
switch (response) {
case null -> { }
case String s
when s.equalsIgnoreCase("YES") -> {
System.out.println("You got it");
}
case String s
when s.equalsIgnoreCase("NO") -> {
System.out.println("Shame");
}
case String s -> {
System.out.println("Sorry?");
}
}
}
`when` 키워드를 사용하여 Pattern에 대한 세분화된 표현할 수 있다.
Record Patterns
Record Patterns을 사용하여 Record 값을 분해해 Java 프로그래밍 언어를 향상하며, Record Patterns 및 Type Patterns을 활용하여 강력하고 선언적이며 구성 가능한 형식의 데이터 탐색과 처리를 제공한다. Record Class의 인스턴스를 분해하여 Pattern matching을 확장해 보다 정교한 데이터 쿼리로 활용할 수 있고, 더 나아가 중첩된 패턴을 사용하여 보다 세밀하게 데이터 쿼리로 활용할 수 있다.
예제
record Point(int x, int y) {}
// As of Java 16
static void printSum(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
// As of Java 21
static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}
Java 21에서는 Pattern matching 후 Record의 필드를 간단하게 다룰 수 있다.
코드가 간결해지고, 명시적인걸 알 수 있다.
record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
위 클래스를 가지고 아래를 설명한다.
// As of Java 21
static void printUpperLeftColoredPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
System.out.println(ul.c());
}
}
첫 번째 예제와 동일한 방식이다.
// As of Java 21
static void printColorOfUpperLeftPoint(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) {
System.out.println(c);
}
}
Patterns matching 후 첫번째 `ColoredPoint`를 보면 `Point`를 인자로 가지고 있는 것을 알 수 있다. 이처럼 Record patterns 안에 다른 Patterns을 중첩하여 외부 Record와 내부 Record를 한 번에 분해할 수 있다.
Rectangle r = new Rectangle(new ColoredPoint(new Point(x1, y1), c1), new ColoredPoint(new Point(x2, y2), c2));
static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c), var lr)) {
System.out.println("Upper-left corner: " + x);
}
}
중첩된 패턴을 사용하면 중첩된 생성자의 구조를 반영하는 코드로 위처럼 분해하여 제공할 수 있으며, `var` 키워드 또한 사용할 수 있다.
// As of Java 21
record Pair(Object x, Object y) {}
Pair p = new Pair(42, 42);
if (p instanceof Pair(String s, String t)) {
System.out.println(s + ", " + t);
} else {
System.out.println("Not a pair of strings");
}
중첩 패턴은 당연히 타입이 일치하지 않을 수 있으며 위처럼 else 문을 통하여 제어가 가능하다.
Sequenced Collections
순서를 가진 Collection을 표현하는 새로운 인터페이스이다. 첫 번째 요소와 마지막 요소에 접근하고 해당 요소를 역순으로 처리하기 위한 일관된 API를 제공한다.
interface SequencedCollection<E> extends Collection<E> {
// new method
SequencedCollection<E> reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
Generational ZGC
Z Garbage Collector(ZGC)를 확장하여 young and old object의 세대를 분리하여 유지함으로써 애플리케이션 성능을 개선한다. 이렇게 하면 ZGC가 대체로 빨리 죽는 young object를 더 자주 수집할 수 있다.
예제
$ java -XX:+UseZGC -XX:+ZGenerational ...
Key Encapsulation Mechanism API
공개 키 암호화를 사용하여 대칭 키를 보호하는 암호화 기술인 Key Encapsulation Mechanism(KEM)을 위한 API이다.
Java의 기존 암호화 API 중 어떤 것도 KEM을 자연스럽게 표현할 수 없었고, 타사 보안 제공업체에서 이미 표준 KEM API에 대한 필요성을 증명해 왔기에 KEM 클래스 구현이 필요했다. 또한 KEM은 양자 공격을 방어하는 데 중요한 도구가 될 걸로 보인다.
public final class KEM {
public static KEM getInstance(String alg)
throws NoSuchAlgorithmException;
public static KEM getInstance(String alg, Provider p)
throws NoSuchAlgorithmException;
public static KEM getInstance(String alg, String p)
throws NoSuchAlgorithmException, NoSuchProviderException;
public static final class Encapsulated {
public Encapsulated(SecretKey key, byte[] encapsulation, byte[] params);
public SecretKey key();
public byte[] encapsulation();
public byte[] params();
}
public static final class Encapsulator {
String providerName();
int secretSize(); // Size of the shared secret
int encapsulationSize(); // Size of the key encapsulation message
Encapsulated encapsulate();
Encapsulated encapsulate(int from, int to, String algorithm);
}
public Encapsulator newEncapsulator(PublicKey pk)
throws InvalidKeyException;
public Encapsulator newEncapsulator(PublicKey pk, SecureRandom sr)
throws InvalidKeyException;
public Encapsulator newEncapsulator(PublicKey pk, AlgorithmParameterSpec spec,
SecureRandom sr)
throws InvalidAlgorithmParameterException, InvalidKeyException;
public static final class Decapsulator {
String providerName();
int secretSize(); // Size of the shared secret
int encapsulationSize(); // Size of the key encapsulation message
SecretKey decapsulate(byte[] encapsulation) throws DecapsulateException;
SecretKey decapsulate(byte[] encapsulation, int from, int to,
String algorithm)
throws DecapsulateException;
}
public Decapsulator newDecapsulator(PrivateKey sk)
throws InvalidKeyException;
public Decapsulator newDecapsulator(PrivateKey sk, AlgorithmParameterSpec spec)
throws InvalidAlgorithmParameterException, InvalidKeyException;
}
Deprecate the Windows 32-bit x86 Port for Removal
향후 Release에서 제거를 목적으로 Windows 32비트 x86 포트가 deprecate 된다.
Prepare to Disallow the Dynamic Loading of Agents
Agent가 실행 중인 JVM에 동적으로 로드될 때 경고를 발생시킨다. 이러한 경고는 기본적으로 무결성을 개선하기 위해 기본적으로 Agent의 동적 로드를 허용하지 않는 향후 릴리스에 대해 사용자를 준비시키는 것을 목표로 한다. startup 시 agent를 로드하는 Serviceability tools은 모든 릴리스에서 경고를 발행하지 않는다.
'Java' 카테고리의 다른 글
Java 21 새로운 기능(2) - preview (1) | 2023.08.28 |
---|---|
Web applications and Project Loom (번역) (0) | 2023.03.12 |
lombok.config 옵션 (0) | 2023.03.05 |
BigDecimal의 toString(), toPlainString(), toEngineeringString() (0) | 2018.09.11 |
Java의 ScheduledExecutorService를 이용해서 스케줄러를 만들어보자 (1) | 2018.05.23 |