java 21이 2023년 9월 19일에 정식으로 출시되었습니다.
차기 LTS 버전으로 중요한 변경점이 있는지 확인해보도록 하겠습니다.
1. 자바 컬렉션을 핸들링하기 쉬워졌습니다.
이와같이 컬렉션들의 상위 인터페이스로 SequenceCollection, SequencedSet, SequencedMap을 상속받아 사용해서 공통된 기능을 제공합니다.
SequencedCollection
sequencedCollection은 처음과 마지막 요소에 대한 공통된 기능을 제공합니다.
reversed()는 기존 컬렉션에서 iterator()나 forEach(), stream()와 같은 기능에서 역순으로 원소들을 처리할 수 있게 됩니다.
interface SequencedCollection<E> extends Collection<E> {
//기존 컬렉션에서 역순으로 원소들을 처리할 수 있게 됩니다.
SequencedCollection<E> reversed();
//첫번째에 원소를 추가합니다.
void addFirst(E);
//마지막에 원소를 추가합니다.
void addLast(E);
//첫번째 원소를 가져옵니다.
E getFirst();
//마지막 원소를 가져옵니다.
E getLast();
//첫번째 원소를 제거합니다.
E removeFirst();
//마지막 원소를 제거합니다.
E removeLast();
}
SequencedSet
SequencedSet은 중복된 원소를 갖지 않는 sequencedCollection에 해당하는 set입니다.
interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
SequencedSet<E> reversed(); // covariant override
}
SequencedMap
SequencedMap은 정해진 순서의 원소에 대한 공통 기능을 제공합니다. 또한 putFirst(K,V)와 putLast(K,V) 메소드는 원소가 이미 존재하는 경우에 적절한 위치로 재배치 되도록 합니다.
interface SequencedMap<K,V> extends Map<K,V> {
// new methods
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
SequencedSet<Entry<K,V>> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
// methods promoted from NavigableMap
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}
2. Virtual Thread
가상 쓰레드는 처리량이 많은 동시성 애플리케이션을 개발하고 모니터링하고 유지 및 관리하는데 드는 비용을 획기적으로 줄여줄 경량 쓰레드입니다.
fun main() = runBlocking {
doWorld()
}
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
자바는 이를 JVM에서 처리하도록 해서 키워드를 명시해주지 않아도 JVM이 알아서 논블로킹 처리를 해주기 때문에 편리하게 사용할 수 있습니다.
스프링은 3 버전부터 지원해주는 걸로 알고 있습니다. 자세한 내용은 추후에 포스팅해서 정리하도록 하겠습니다.
3.Record
레코드에 타입 패턴을 함께 적용하여 레코드의 값을 손쉽게 처리할 수 있도록 도와줍니다.
자바 16에서는 instace of 연산자에 타입 패턴을 적용하여 패턴을 매칭시키도록 개선해서 하위 블록에 직접적으로 타입 캐스팅 할 필요가 없어졌습니다.
// 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 ...
}
자바 21부터는 레코드 타입에 대해 보다 간편하게 사용할 수 있도록 변경됩니다.
//Java 16
record Point(int x, int y) {}
static void printSum(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
//Java 21
tatic void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}
위와 같은 패턴 매칭이 개선되어서 기존의 instance of에 if-else문법을 사용하지 않고 스위치 문에서 보다 간편하게 타입여부를 검사할 수 있습니다.
// 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();
};
}
4.Null Check
기존에는 파라미터가 null이면 NPE(NullPointerException)을 던지기 때문에, null에 대한 검사를 외부에서 수행했어야 하지만 자바21부터는 null에 해당하는 케이스를 내부에서 검사할 수 있게 되었다
// 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");
}
}
5.case 세분화
case문은 여러 값에 대한 검사를 필요로 하기 때문에 상당히 복잡한 구조로 만들 수 밖에 없었지만 자바 21에서는 보기 좋게 변경되었습니다.
// 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?");
}
}
}
6. enum개선
가독성이 좋게 enum이 개선되었습니다.
// As of Java 21
sealed interface CardClassification permits Suit, Tarot {}
public enum Suit implements CardClassification { CLUBS, DIAMONDS, HEARTS, SPADES }
final class Tarot implements CardClassification {}
static void exhaustiveSwitchWithoutEnumSupport(CardClassification c) {
switch (c) {
case Suit s when s == Suit.CLUBS -> {
System.out.println("It's clubs");
}
case Suit s when s == Suit.DIAMONDS -> {
System.out.println("It's diamonds");
}
case Suit s when s == Suit.HEARTS -> {
System.out.println("It's hearts");
}
case Suit s -> {
System.out.println("It's spades");
}
case Tarot t -> {
System.out.println("It's a tarot");
}
}
}
// As of Java 21
static void exhaustiveSwitchWithBetterEnumSupport(CardClassification c) {
switch (c) {
case Suit.CLUBS -> {
System.out.println("It's clubs");
}
case Suit.DIAMONDS -> {
System.out.println("It's diamonds");
}
case Suit.HEARTS -> {
System.out.println("It's hearts");
}
case Suit.SPADES -> {
System.out.println("It's spades");
}
case Tarot t -> {
System.out.println("It's a tarot");
}
}
}
그 외
[ JEP 439: Generational ZGC ]
ZGC는 짧은 지연 시간과 높은 확정성을 위해 고안된 GC 알고리즘으로 Java 15부터 프로덕션 환경에서 사용할 수 있게 되었다. 약한 세대 가설(Weak Generational Hypothesis)을 따라 대부분의 객체는 금방 죽기 때문에, 금방 죽는 Young 영역과 오래 살아남는 Old 영역을 분리하여 관리하는 것이 좋다. 그래서 Java 21에서는 이러한 방식을 통해 ZGC의 기능을 확장하여 성능을 개선시키고자 하였다. ZGC와 관련된 자세한 내용은 추후의 별도 포스팅을 통해 살펴볼 예정이다.
[ JEP 449: Deprecate the Windows 32-bit x86 Port for Removal ]
이후 릴리스에서 Windows 32bit x86 port를 제거하기 위해, 이를 Deprecate 시켰다. 이제 Windows 32bit 용 빌드를 구성하려고 시도할 때 오류 메세지가 표시된다.
$ bash ./configure
...
checking compilation type... native
configure: error: The Windows 32-bit x86 port is deprecated and may be removed in a future release. \\
Use --enable-deprecated-ports=yes to suppress this error.
configure exiting with result code 1
$
[ JEP 451: Prepare to Disallow the Dynamic Loading of Agents ]
자바는 agent를 통해 애플리케이션 코드를 동적으로 변경하도록 지원해왔고, 이를 통해 애플리케이션을 모니터링하고 관찰하는 많은 방법들이 탄생하게 되었다. 대표적으로 Pinpoint와 같은 도구는 자바 에이전트를 기반으로 바이트 코드를 조작하여 모니터링을 돕는 APM 도구이다.
이러한 자바 애플리케이션을 프로파일링하는 정상적인 방법들은 애플리케이션이 실행될 때 불러와지고, 애플리케이션이 실행되는 중간에 불러와지는 경우가 거의 없다. 따라서 자바 21에서는 실행 중인 JVM에 에이전트가 동적으로 로드될 때 경고를 발행시키도록 수정되었다. 물론 JVM 시작 시에 에이전트를 로드하는 것은 경고를 발생시키지 않는다.
[ JEP 452: Key Encapsulation Mechanism API ]
Java21에는 공개 키 암호화를 사용하여 대칭 키를 보호하는 암호화 기술인 KEM(Key Encapsulation Mechanism) API가 도입되었다. 기존의 기술은 무작위로 생성된 대칭 키를 공개 키로 암호화하는 것이지만, 패딩이 필요하고 보안을 증명하기 어려울 수 있다. 대신 KEM은 공개 키의 속성을 사용하여 패딩이 필요 없는 관련 대칭 키를 도출한다.
KEM은 양자 공격을 방어하기 위한 핵심 도구가 될 것이다. 다른 보안 제공업체들은 이미 표준 KEM API에 대한 필요성을 표명했고, 자바 역시 이를 공식적으로 도입하기로 결정하였다.
package javax.crypto;
public class DecapsulateException extends GeneralSecurityException;
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;
}
'컴퓨터 > Java' 카테고리의 다른 글
Java8 / Java 11 차이 (2) | 2021.11.07 |
---|