Java 17의 새로운 기능
1. JEP(JDK Enhancement Proposal) 목록
1.1 항상 엄격한 부동 소수점 (JEP 306)
이 JEP는 주로 과학 어플리케이션을 위한 것으로 부동 소수점 연산을 일관성 있게 엄격하게 만든다. 기본 부동 소수점 연산은 strict이거나 strictfp이며, 이 둘은 모든 플랫폼에서 부동 소수점 연산으로부터 동일한 결과를 보장한다.
자바 1.2 이전에는 strictfp 동작도 기본이었다. 하지만 하드웨어 문제로 인해 아키텍처가 바뀌었고 strictfp라는 키워드는 그러한 동작을 다시 활성화하기 위해 필요했다. 이제 더 이상 이 키워드를 사용할 필요가 없다.
1.2 향상된 Pseudo-Random Number Generators (JEP 356)
더 특별한 사용 사례와 관련된 JEP 356은 Pseudo-Random Number Generators(PRNG)를 위한 새로운 인터페이스 및 구현을 제공한다. 따라서 다양한 알고리즘을 상호 교환하여 사용하는 것이 더 쉽고 스트림 기반 프로그래밍에 대한 더 나은 지원을 제공한다.
public IntStream getPseudoInts(String algorithm, int streamSize) {
// returns an IntStream with size @streamSize of random numbers generated using the @algorithm
// where the lower bound is 0 and the upper is 100 (exclusive)
return RandomGeneratorFactory.of(algorithm)
.create()
.ints(streamSize, 0,100);
}
java.util.Random, SplittableRandom and SecureRandom와 같은 레거시 random 클래스들은 이제 새로운 RandomGenerator 인터페이스를 구현한다.
1.3 새로운 macOS 렌더링 파이프라인 (JEP 382)
이 JEP는 애플이 Swing GUI에서 내부적으로 사용되는 OpenGL API (macOS 10.14)를 deprecate 처리 했기 때문에, macOS용 자바 2D 내부 렌더링 파이프라인을 제공한다. 새로운 구현은 애플 메탈 API를 사용하며 내부 엔진을 제외하고는 기존 API에 변경 사항이 없다.
1.4 macOS/AArch64 Port (JEP 391)
애플은 자사의 컴퓨터 라인을 X64에서 AArch64로 전환하는 장기 계획을 발표했다. 이 JEP는 맥OS 플랫폼에서 AArch64에서 실행되도록 JDK를 포팅한다.
1.5 Deprecate the Applet API for Removal (JEP 398)
Applet API를 이용해 개발 경력을 시작한 많은 자바 개발자들에게 이것은 슬플지 모르지만, 많은 웹 브라우저들은 이미 자바 플러그인에 대한 지원을 없앴다. API가 관련성이 없어지자, Java 9에 Deprecate되었던 이 API에 대한 for removal = true 표시를 했다.
1.6 강하게 캡슐화된 JDK Internals (JEP 403)
JEP 403은 –illegal-access
flag를 제거함으로써 JDK internals를 강하게 캡슐화하는데 한 단계 더 나아갔다. 플랫폼은 이 flag를 무시할 것이고, 만약 이 flag 값이 존재한다면 콘솔은 이 flag에 대한 사용 중단에 대한 메세지를 발행할 것이다.이 기능은 sun.misc.Unsafe와 같은 중요한 API를 제외하고 JDK internals에 사용자가 접근하는 것을 방지한다.
1.7 Switch 구문에 대한 패턴 매칭 (Preview) (JEP 406)
스위치 구문에 대한 패턴 매칭 기능이 추가되었다. 이로써 보일러플레이트 코드가 줄고 언어의 표현성이 향상되었다.
아래 예시를 통해 이해를 해보자.
static record Human (String name, int age, String profession) {}
public String checkObject(Object obj) {
return switch (obj) {
case Human h -> "Name: %s, age: %s and profession: %s".formatted(h.name(), h.age(), h.profession());
case Circle c -> "This is a circle";
case Shape s -> "It is just a shape";
case null -> "It is null";
default -> "It is an object";
};
}
public String checkShape(Shape shape) {
return switch (shape) {
case Triangle t && (t.getNumberOfSides() != 3) -> "This is a weird triangle";
case Circle c && (c.getNumberOfSides() != 0) -> "This is a weird circle";
default -> "Just a normal shape";
};
}
1.8 RMI Activation 제거 (JEP 407)
Java 15에서 for removal = true로 표시되었던 것이 Java 17에서 제거되었다.
1.9 Sealed class (JEP 409)
Java 15, 16에서 Preview 모드였던 Sealed class가 Java 17에서 공식적으로 언어의 새로운 기능이 되었다.
이 기능은 어떠한 클래스나 인터페이스가 이 Sealed class를 상속/구현할지의 여부를 제한한다. 새로운 Switch 구문의 패턴매칭과 함께 사용되면서 더 세련되고 깔끔한 타입 조사와 캐스팅이 가능하게 되었다. 아래 예시를 살펴보자.
int getNumberOfSides(Shape shape) {
return switch (shape) {
case WeirdTriangle t -> t.getNumberOfSides();
case Circle c -> c.getNumberOfSides();
case Triangle t -> t.getNumberOfSides();
case Rectangle r -> r.getNumberOfSides();
case Square s -> s.getNumberOfSides();
};
}
1.10 AOT/JIT 컴파일러 제거 (JEP 410)
실험 기능으로 JDK 9와 JDK 10에 각각 도입된 AOT(Ahead-Of-Time) 컴파일러(JEP 295)와 GraalVM(JEP-317)의 JIT(Just-In-Time) 컴파일러는 유지 보수 비용이 높은 기능이었다.
이들은 별다른 채택을 받지 못했다. 이 때문에 이 JEP는 이들을 플랫폼에서 제외시켰지만, 개발자들은 여전히 그랄VM을 이용해 이들을 활용할 수 있다.
1.11 Deprecate the Security Manager for Removal (JEP 411)
Security Manager는 클라이언트 측 자바 코드를 보안하는 것을 목표로 했지만 더 이상 관련성이 없기 때문에 제거하도록 표시되었다.
1.12 Foreign Function and Memory API (Incubator) (JEP 412)
Foreign Function and Memory API를 통해 자바 개발자들은 JVM 외부에서 코드에 접근하여 힙 밖으로 메모리를 관리할 수 있다. JNI API를 대체하여 기존보다 보안성과 성능을 향상시키는 것이 목표이다.
이 API는 프로젝트 파나마에 의해 개발된 또 다른 기능이며, JEP 393, 389, 383 및 370에 의해 진화되고 선행되었다.
이 기능을 활용하여 우리는 Java class에서 C 라이브러리를 호출할 수 있다.
먼저 API를 통해 호출하고자 하는 라이브러리를 로드할 필요가 있다. 이후에 타겟 메서드 시그니처를 명시하고 호출한다.
public class JEP412 {
private static final SymbolLookup libLookup;
static {
// loads a particular C library
var path = JEP412.class.getResource("/print_name.so").getPath();
System.load(path);
libLookup = SymbolLookup.loaderLookup();
}
public String getPrintNameFormat(String name) {
var printMethod = libLookup.lookup("printName");
if (printMethod.isPresent()) {
var methodReference = CLinker.getInstance()
.downcallHandle(
printMethod.get(),
MethodType.methodType(MemoryAddress.class, MemoryAddress.class),
FunctionDescriptor.of(CLinker.C_POINTER, CLinker.C_POINTER)
);
try {
var nativeString = CLinker.toCString(name, ResourceScope.newImplicitScope());
var invokeReturn = methodReference.invoke(nativeString.address());
var memoryAddress = (MemoryAddress) invokeReturn;
return CLinker.toJavaString(memoryAddress);
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
}
throw new RuntimeException("printName function not found.");
}
}
1.13 Vector API (Second Incubator) (JEP 414)
벡터 API는 병렬로 실행되는 다양한 명령어 집합을 의미하는 SIMD(Single Instruction, Multiple Data) 유형의 연산을 다룬다. 벡터 명령어를 지원하고 파이프라인과 같은 명령어의 실행을 허용하는 특수 CPU 하드웨어를 활용한다.
결과적으로, 새로운 API는 개발자들이 기본 하드웨어의 잠재력을 활용하여 보다 효율적인 코드를 구현할 수 있게 해줄 것이다.
이 연산에 대한 일상적인 사용 사례는 과학 대수 선형 응용, 이미지 처리, 문자 처리, 및 임의의 무거운 산술 응용 또는 다수의 독립 피연산자에 대한 연산을 적용해야 하는 임의의 응용이다.
API를 사용하여 간단한 벡터 곱셈 예를 설명해 보자
public void newVectorComputation(float[] a, float[] b, float[] c) {
for (var i = 0; i < a.length; i += SPECIES.length()) {
var m = SPECIES.indexInRange(i, a.length);
var va = FloatVector.fromArray(SPECIES, a, i, m);
var vb = FloatVector.fromArray(SPECIES, b, i, m);
var vc = va.mul(vb);
vc.intoArray(c, i, m);
}
}
public void commonVectorComputation(float[] a, float[] b, float[] c) {
for (var i = 0; i < a.length; i ++) {
c[i] = a[i] * b[i];
}
}
1.14 컨텍스트-특정적 역직렬화 필터 (JEP 415)
Java 9에 처음 도입된 JEP 290은 많은 보안 문제의 공통 소스인 신뢰할 수 없는 소스로부터 들어오는 직렬화된 데이터를 검증할 수 있게 해주었다. 그 검증은 JVM 레벨에서 이루어지며, 더 많은 보안과 견고성을 제공한다.
JEP 415로, 애플리케이션들은 JVM 레벨에서 정의된 컨텍스트-특정적이고 동적으로 선택된 병렬화 필터들을 구성할 수 있다. 각각의 병렬화 동작은 그러한 필터들을 호출할 것이다.
※ 참조