Java 17
2021년 9월 14일 Java LTS 인 JDK 17 GA가 릴리즈 되었습니다. JDK 17은 최대 2029년 9월까지 업데이트가 제공될 예정인데요, 2018년 9월에 JDK 11이 릴리즈된 이후 3년만에 출시된 LTS 버전의 JDK 입니다.
코드의 변화뿐만 아니라 LTS 출시 주기가 3년에서 2년으로 변경되는 등 많은 변화가 있었는데요, 이 글에서는 Java 17의 새로운 기능들에 대해 정리해 보겠습니다.
JEP List
1. Restore Always-Strict Floating-Point Semantics (JEP 306)
이것은 과학적인 목적으로 수치에 민감한 프로그램을 위한 JEP 입니다. 이는 다시 기본 부동 소수점 연산을 strict
또는 strictfp
로 설정하여 모든 플랫폼에서 부동 소수점 계산을 할 경우 동일한 결과를 얻을 수 있게 합니다. (일관성 보장)
- Java 1.2 이전에는 모든 부동소수점 계산이
strict
하여 x87 기반 하드웨어에 과열을 일으켰습니다. - Java 1.2 이후에는 과열을 방지하기 위해 부동 소수점 계산을 위해
strictfp
키워드를 필요로 합니다. - 오늘날에는 Intel과 AMD 모두 과열 없이 엄격한 부동소수점 연산을 지원할 수 있는 SIMD Extensions 2 (Streaming SIMD Extensions 2) 확장을 지원하므로, x87 기반 하드웨어에서 문제가 발생하지 않습니다.
- 따라서 Java 17에서는 Java 1.2 이전 버전의
strict
한 부동소수점 연산 방식을 기본값으로 복원합니다. 이는strictfp
키워드는 선택사항임을 의미합니다.
2. Enhanced Pseudo-Random Number Generators (JEP 356)
유사난수 알고리즘을 구현하거나 사용하기 쉽게 하기 위해 RandomGenerator
라 불리는 새로운 인터페이스를 제공합니다.
이로 인해 서로 다른 알고리즘을 자유롭게 사용할 수 있습니다. 또한 스트림 기반 프로그래밍을 보다 효율적으로 지원합니다.
1
2
3
4
5
package java.util.random;
public final class RandomGeneratorFactory<T extends RandomGenerator> {
// ...
}
RandomGenerator
을 구현한 모든 알고리즘을 사용하여 1 ~ 100 사이의 정수들의 집합에서 랜덤한 5개의 숫자를 불러오는 코드입니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Main {
public static void main(String[] args) {
var randomGeneratorList = RandomGeneratorFactory.all()
.map(RandomGeneratorFactory::name)
.collect(Collectors.toList());
randomGeneratorList.forEach(generator -> {
RandomGenerator rg = RandomGeneratorFactory.of(generator).create(999);
System.out.println(rg.getClass());
int counter = 0;
while (counter <= 5) {
int result = rg.nextInt(100);
System.out.println(result);
counter ++;
}
System.out.println("=======================");
});
}
}
다음의 코드를 통해 모든 알고리즘을 확인할 수도 있습니다.
1
2
3
4
RandomGeneratorFactory.all()
.map(fac -> fac.group()+ " : " +fac.name())
.sorted()
.forEach(System.out::println);
1
2
3
4
5
6
7
8
9
10
11
12
13
LXM : L128X1024MixRandom
LXM : L128X128MixRandom
LXM : L128X256MixRandom
LXM : L32X64MixRandom
LXM : L64X1024MixRandom
LXM : L64X128MixRandom
LXM : L64X128StarStarRandom
LXM : L64X256MixRandom
Legacy : Random
Legacy : SecureRandom
Legacy : SplittableRandom
Xoroshiro : Xoroshiro128PlusPlus
Xoshiro : Xoshiro256PlusPlus
java.util.Random
패키지에 있는 SplitRandom
클래스나 SecureRandom
같은 클래스같은 레거시 클래스들은 JDK 17에서는 RandomGenerator
인터페이스를 확장하여 구현되었습니다.
3. New macOS Rendering Pipeline (JEP 382)
이 JEP는 애플이 스윙 GUI 프로그래밍에서 내부적으로 사용되는 OpenGL API
를 deprecate 처리하였기 때문에 새롭게 macOS용 자바 2D 내부 렌더링 파이프라인을 구현합니다. 새로운 구현체는 애플 메탈 API를 사용하며 내부 엔진 외 기존 API에 대한 변경 사항은 없습니다.
4. macOS/AArch64 Port (JEP 391)
애플은 자사의 컴퓨터 라인을 x64에서 AArch64로 전환하는 장기 계획을 발표하였습니다. 이 JEP는 JDK가 macOS의 AArch64에서 실행되도록 코드를 포팅합니다.
5. Deprecate the Applet API for Removal (JEP 398)
이미 많은 웹 브라우저들이 자바 플러그인에 대한 지원을 중지하였습니다. API가 무의미해짐에 따라 JDK 9부터 deprecate 처리되었고, Applet API는 향후 릴리즈 버전에서 제거될 예정입니다.
1
2
3
4
5
@Deprecated(since = "9", forRemoval = true)
@SuppressWarnings("removal")
public class Applet extends Panel {
// ...
}
6. Strongly Encapsulate JDK Internals (JEP 403)
많은 써드파티 라이브러리, 프레임워크 혹은 도구들이 JDK 내부의 API나 패키지로 접근하고 있습니다. Java 16은 JEP 396를 통해 기본적으로 강력한 캡슐화를 기본전략으로 설정합니다. 하지만 단순 캡슐화로의 전환을 지원하기 위해 --illegal-access
옵션을 제공했었습니다.
JEP 403은 --illegal-access
옵션을 제거하였습니다. 이제 JDK의 강력한 내부 캡슐화로 한걸음 더 다가갔습니다.
추가로 reflection 을 통한 private field / method 접근이 불가능하게 됩니다.
1
2
3
entityField.setAccessible(true);
Object object = entityField.get(this.path);
7. Pattern Matching for Switch (Preview / JEP 406)
이 JEP는 switch 문과 표현식을 위한 패턴 매칭을 추가하였습니다. Preview 버전이기 때문에 --enable-preview
옵션을 사용하여 기능을 활성화 시켜야 합니다.
7-1. if else chain
JDK 16에서 instanceof 에 대한 패턴 매칭이 추가되었는데, 이를 통해 instanceof 관용구를 단순화할 수 있었습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
private static String stringFormatter(Object obj) {
String result = null;
if (obj instanceof Integer i) {
result = String.format("int %d", i);
} else if (obj instanceof Long l) {
result = String.format("long %d", l);
} else if (obj instanceof String s) {
result = String.format("String %s", s);
}
return result;
}
JDK 17에서는 위의 코드를 switch 문을 이용한 코드로 변환할 수 있습니다.
1
2
3
4
5
6
7
8
private static String stringFormatter(Object obj) {
return switch (obj) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case String s -> String.format("string %s", s);
default -> throw new IllegalStateException("Unexpected value: " + obj);
};
}
7-2. null check in switch
이제 switch 문안에서 null 체크를 할 수 있습니다. 더이상 switch 이전에 null 체크를 하지 않아도 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static void stringTester (String s) {
// this is not necessary
// if (s == null) {
// System.out.println("string is null");
// return;
// }
switch (s) {
case null -> System.out.println("string is null");
case "Java 11", "Java 17" -> System.out.println("java LTS");
case "Java 8" -> System.out.println(s);
default -> System.out.println("nothing matched");
}
}
7-3 refined pattern in switch
아래 코드를 살펴보면, Car
객체의 바퀴수를 테스트하기 위해선 switch문 안에서 if 조건절을 사용해야만 했습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
abstract static class Transportation { }
static class Car extends Transportation {
int wheel;
int countWheel() {
return this.wheel;
}
public Car(int wheel) {
this.wheel = wheel;
}
}
private static void testTransportation (Transportation transportation) {
switch (transportation) {
case null:
System.out.println("tp is null");
break;
case Car car:
if (car.countWheel() == 4) {
System.out.println("car with four wheel");
} else {
System.out.println("car");
}
break;
default:
System.out.println("Unknown");
break;
}
}
Java 17 은 소위 redefined pattern
혹은 guarded pattern
이라 불리는 패턴을 허용합니다.
1
2
3
4
5
6
7
8
private static void testTransportation (Transportation transportation) {
switch (transportation) {
case null -> System.out.println("tp is null");
case Car car && car.countWheel() == 4 -> System.out.println("car with four wheel");
case Car car -> System.out.println("car");
default -> System.out.println("Unknown");
}
}
8. Remove RMI Activation (JEP 407)
RMI Activation
과 java.rmi.activation
패키지를 제거하였습니다.
9. Sealed Classes (JEP 409)
Java 15 와 16 에서 preview 기능으로 소개된 봉인 클래스가 표준 기능으로 확정되었습니다. Java 16에서의 변경점은 없습니다.
이 기능은 봉인된 구성 요소를 확장하거나 구현할 수 있는 다른 클래스 혹은 인터페이스를 제한합니다. JEP 406 (패턴 매칭)과 결합하면 다음과 같이 깔끔하게 타입, 캐스트 패턴을 통해 검사할수 있습니다.
1
2
3
4
5
6
7
8
int getWheel(Transportation transportation) {
return switch (transportation) {
case Car c -> c.countWheel();
case MotorCycle m -> m.countWheel();
case Subway s -> s.countWheel();
case Airplane a -> a.countWheel();
};
}
10. Remove the Experimental AOT and JIT Compiler (JEP 410)
AOT와 JIT는 JDK 9 와 JDK 10 에 각각 도입된 기능입니다. 이들은 유지 보수 비용이 많이 드는 기능이었습니다. 하지만 그리 많이 사용되지 않는 기능이기 때문에 JEP는 이 기능들을 삭제하기로 하였습니다.
그와 별개로 개발자는 여전히 GraalVM을 사용하여 이 기능들을 활용할 수 있습니다.
11. Deprecate the Security Manager for Removal (JEP 411)
Security Manager은 Java 1.0 에서 클라이언트측 Java 코드를 보호하기 위해 소개된 기능입니다만 더이상 사용되지 않습니다. 추후 릴리즈 버전에서 제거하기 위해 deprecate 처리합니다.
1
2
3
4
@Deprecated(since="17", forRemoval=true)
public class SecurityManager {
// ...
}
12. Foreign Function and Memory API (Incubator) (JEP 412)
자바 개발자들은 외부 함수 및 메모리 API를 통해 JVM 외부에서 코드에 접근하고 힙 외부의 메모리를 관리할 수 있게 되었습니다. 이 API의 목표는 JNI API를 대체하고 보안과 성능을 향상시키는 것입니다.
13. Vector API (Second Incubator) (JEP 414)
Java 16에서 JEP 414는 새로운 Vector API를 Incubating API로 소개하였습니다.
이 JEP는 Vector API 의 성능 및 문자 지원 작업 등의 향상된 기능을 제공합니다.
14. Context-Specific Deserialization Filters (JEP 415)
Java 9에 처음 도입된 JEP 290 기능은 많은 보안 문제의 공통 원인은 신뢰할 수 없는 코드에서 들어오는 직렬 데이터를 검증할 수 있게 해주었습니다. 이러한 검증은 JVM 레벨에서 수행되기 때문에 더 높은 보안성과 견고성이 요구됩니다.
JEP 415를 사용하면 애플리케이션은 JVM 수준에서 정의된 컨텍스트별 및 동적으로 선택된 역직렬화 필터를 구성할 수 있습니다. 각 역직렬화 작업은 이러한 필터를 호출합니다.