파란하늘의 지식창고
Published 2021. 9. 15. 05:48
JDK 17 New Features Study/Java
반응형

JDK의 버전별 변경 사항은 이곳을 참고하세요.

JDK 12 ~ 17 사이 추가된 language specification feature는 이곳을 참고하세요.


JDK 17은 변경된 릴리즈 정책에 따라 3년 만에 나오는 LTS 버전이다.

따라서 향후 JDK 23이 나오기 전까지 대부분의 경우 JDK 11 -> JDK 17로 변경하여 사용하게 된다.

Spec

Java SE 17 Platform JSR 392에 정의된 바와 같이 JSR 392 구현이 목표

실제 Spec은 Final Release Specification 문서를 참고해야 함

Final Release Specification Feature Summary

전체 JEP Feature 목록은 OpenJDK의 JDK17 문서로 확인할 수 있다.

Component Feature
specification / language Restore Always-Strict Floating-Point Semantics
core-libs / java.util Enhanced Pseudo-Random Number Generators
client-libs / 2d New macOS Rendering Pipeline
hotspot macOS/AArch64 Port
client-libs / java.awt Deprecate the Applet API for Removal
- Strongly Encapsulate JDK Internals
specification / language Pattern Matching for switch (Preview)
core-libs / java.rmi Remove RMI Activation
specification / language Sealed Classes
hotspot / compiler Remove the Experimental AOT and JIT Compiler
security-libs / java.security Deprecate the Security Manager for Removal
core-libs Foreign Function & Memory API (Incubator)
core-libs / java.io:serialization Context-Specific Deserialization Filters

JEP 306: Restore Always-Strict Floating-Point Semantics

항상 엄격한 부동 소수점 체계를 복원

엄격한 부동 소수점 체계 (strict floating-point semantic)와 미묘하게 다른 기본 부동 소수점 체계 (default floating-point semantic)를 병행 사용하지 않고 일관되게 엄격한 부동 소수점 체계를 사용하도록 하였다.

Java SE 1.2에서 strict와 default가 도입되기 전의 체계로 language와 vm을 복원한다.

1990년대 후반에 default floating-point semantic을 변경하게 된 동기는 원래 java language와 JVM semantic 간 잘못된 상호 작용과 인기 있는 x86 아키텍처의 x87 floting-point co-processor instruction set의 일부 특성에서 비롯되었다.

비정상 피연산자 및 결과를 포함하여 모든 경우에 정확한 floating-point semantic을 일치시키려면 추가 명령어의 큰 overhead가 필요하였다.

overflow 또는 underflow가 없을 때 결과를 일치시키는 것은 더 적은 overhead로 수행될 수 있으며 이는 java SE 1.2에 도입된 default floating-point semantic에서 허용하는 것이다.

하지만 2001년경부터 펜티엄 4 이상 프로세서에 탑재된 SSE2(Streaming SIME Extension 2) 확장은 과도한 overhead 없이 간단한 방식으로 strict JVM floating-point 연산을 지원할 수 있게 되었다.

JEP 356: Enhanced Pseudo-Random Number Generators

향상된 PRNG 지원

점프 가능한 (jumpable) PRNG, 추가 클래스의 분할 가능한 (splittable) PRNG algorithm(LXM)을 포함하여 pseudorandom number generator에 대한 새로운 interface type 및 implementation을 제공한다.

JEP 382: New macOS Rendering Pipeline

더 이상 사용되지 않는 OpenGL API를 사용하는 기존 pipeline의 대안으로 Apple Metal API를 사용하는 macOS용 새로운 rendering pipeline

JEP 391: macOS/AArch64 Port

Macintosh 컴퓨터를 x64에서 AArch64로 전환하려는 apple의 계획에 따라 JDK를 MacOS/AArch64로 이식한다.

JEP 398: Deprecate the Applet API for Removal

더 이상 사용하지 않는 Applet API 제거를 위해 deprecate 처리

JEP 403: Strongly Encapsulate JDK Internals

sun.misc.Unsafe와 같은 중요한 내부 API를 제외하고 JDK의 모든 내부 요소를 강력하게 캡슐화한다.
JDK 9~16에서 사용한 single command-line option으로 이에 대한 완화 처리는 JDK 17에서는 사용할 수 없다.

덧.
이로 인해 reflection을 통한 private field 접근에 대해 더 이상 동작하지 않으므로 JDK 버전 변경 시 확인이 필요하다.
아래와 같은 코드는 더 이상 동작하지 않는다.

var ks = java.security.KeyStore.getInstance("jceks");
var f = ks.getClass().getDeclaredField("keyStoreSpi");
f.setAccessible(true);

JEP 406: Pattern Matching for switch (Preview)

switch 구문에 대한 pattern matching이 preview로 추가되었다.
JDK 16에서 instanceof에 대한 Pattern Matching이 추가되었는데 이를 통해 instanceof-and-cast 관용구를 단순화할 수 있었다.

// Old code
if (o instanceof String) {
    String s = (String)o;
    ... use s ...
}

// New code
if (o instanceof String s) {
    ... use s ...
}

조건절이 많아지는 경우 다음과 같이 작성해야 한다.

static String formatter(Object o) {
    String formatted = "unknown";
    if (o instanceof Integer i) {
        formatted = String.format("int %d", i);
    } else if (o instanceof Long l) {
        formatted = String.format("long %d", l);
    } else if (o instanceof Double d) {
        formatted = String.format("double %f", d);
    } else if (o instanceof String s) {
        formatted = String.format("String %s", s);
    }
    return formatted;
}

이제 이에 대해서도 switch 구문에서 Pattern Matching을 통해 다음과 같이 간결하게 작성할 수 있게 된다.

static String formatterPatternSwitch(Object o) {
    return switch (o) {
        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        -> o.toString();
    };
}

Pattern matching and null

기존의 switch 구문은 표현식에서 결괏값이 null이 되는 경우 NullPointerException이 발생하기 때문에 다음과 같이 switch 구문 외부에서 null에 대한 테스트를 수행해야 했다.

static void testFooBar(String s) {
    if (s == null) {
        System.out.println("oops!");
        return;
    }
    switch (s) {
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}

이에 대해서도 다음과 같이 사용할 수 있게 된다.

static void testFooBar(String s) {
    switch (s) {
        case null         -> System.out.println("Oops");
        case "Foo", "Bar" -> System.out.println("Great");
        default           -> System.out.println("Ok");
    }
}

case에 null인 경우가 없으면 이전과 마찬가지로 null 발생 시 NullPointerException을 발생시킨다.
다른 case label과 같은 방식으로 null을 처리하기를 원할 수 있다.
예를 들어 다음 코드에서 String s는 null 값과 모든 String 값에 match 된다.

static void testStringOrNull(Object o) {
    switch (o) {
        case null, String s -> System.out.println("String: " + s);
    }
}

Refining patterns in switch

Shape 값을 전환하는 다음 코드의 경우를 보자.

class Shape {}
class Rectangle extends Shape {}
class Triangle  extends Shape { int calculateArea() { ... } }

static void testTriangle(Shape s) {
    switch (s) {
        case null:
            break;
        case Triangle t:
            if (t.calculateArea() > 100) {
                System.out.println("Large triangle");
                break;
            }
        default:
            System.out.println("A shape, possibly a small triangle");
    }
}

이 코드의 목적은 큰 삼각형에(100 이상의 면적) 대한 특별한 case와 다른 모든 것 (작은 삼각형 포함)에 대한 default case를 갖는 것이다.
그러나 이것을 하나의 pattern으로 표현할 수 없다.
먼저 모든 삼각형과 일치하는 case label을 작성한 다음 해당 명령문 그룹 내에서 다소 불편하게 삼각형 영역 테스트를 배치해야 한다.
그런 다음 삼각형의 면적이 100보다 작을 때 올바른 동작을 얻기 위해 fall-throuth를 사용해야 한다. (if block 내부에 break를 조심스럽게 배치해야 한다.)

여기서 문제는 case를 구별하기 위해 단일 pattern을 사용하는 것이 단일 조건 이상으로 확장되지 않는다는 것이다.
한 가지 접근 방식은 casel label을 세분화하는 것일 수 있다. 이러한 방법을 다른 프로그래밍 언어에서는 guard라고 불린다.
예를 들어 case label 끝에 표시되고 그 뒤에 boolean expression이 오는 새 keyword를 도입할 수 있다.
(예: case Trangle t where t.cacluateArea() > 100

그러나 더 표현적인 접근 방식이 있다.
case label의 기능을 확장하는 대신 pattern 자체의 language를 확장할 수 있다.
임의의 boolean expresion b로 pattern p를 정제할 수 있도록 하는 guarded pattern이라고 하는 새로운 종류의 pattern p && b를 추가할 수 있다.

이 접근 방식을 사용하면 testTrangle 코드를 다시 방문하여 큰 삼각형의 특수한 경우를 직접 표현할 수 있다.
이렇게 하면 switch 문에서 fall-through를 사용하지 않아도 되므로 간결한 화살표 스타일 (->) 규칙을 사용할 수 있다.

static void testTriangle(Shape s) {
    switch (s) {
        case Triangle t && (t.calculateArea() > 100) ->
            System.out.println("Large triangle");
        default ->
            System.out.println("A shape, possibly a small triangle");
    }
}

s의 값은 먼저 유형 패턴 Trangle t와 일치하는 경우, 그다음 표현식 (t.calculateArea() > 100)이 true인지 평가한다.

switch를 사용하면 application 요구 사항이 변경될 때 case label을 쉽게 이해하고 변경할 수 있다.
예를 들어 삼각형을 기본 경로에서 분할할 수 있다.
정제된 패턴과 정제되지 않은 패턴을 모두 사용하여 이를 수행할 수 있다.

static void testTriangle(Shape s) {
    switch (s) {
        case Triangle t && (t.calculateArea() > 100) ->
            System.out.println("Large triangle");
        case Triangle t ->
            System.out.println("Small triangle");
        default ->
            System.out.println("Non-triangle");
    }
}

Description

두 가지 방법으로 switch 문과 표현식을 향상한다.

  • 상수 외에 pattern을 포함하도록 case label을 확장
  • guarded pattern과 괄호 안 pattern의 두 가지 새로운 패턴

Patterns in switch labels

새로운 case p switch label을 도입한다.
여기서 p는 pattern이다.
selector expression의 값이 switch label과 비교되고 label 중 하나가 선택되고 해당 label과 연결된 코드가 실행된다.
차이점은 pattern이 있는 case label의 경우 해당 선택이 동일성 검사가 아닌 pattern 일치에 의해 결정된다는 것이다.
예를 들어 다음 코드에서 o 값은 Long l pattern과 일치하고 Long l case와 관련된 코드가 실행된다.

Object o = 123L;
String formatted = switch (o) {
    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        -> o.toString();
};

case label이 pattern을 가질 수 있는 경우 다음과 같은 네 가지 주요 설계 문제가 있다.

  • 향상된 type check
  • switch expression 및 구문 완성도
  • pattern 변수 선언의 범위
  • null 처리

각각의 경우에 대한 자세한 설명은 openjdk의 JEP 406 문서를 참조하면 된다.

JEP 407: Remove RMI Activation

RMI의 나머지 부분은 유지하면서 RMI(Remote Method Invocation) activation mechanism은 제거하였다.

JEP 409: Sealed Classes

Sealed Classes의 경우 JDK 15에서 preview로 제안되었고 JDK 16에서 JEP 397에 의해 개선되어 다시 preview로 제안되었다.
JDK 17에서는 JDK 16에서 변경사항 없이 마무리되었다.

Sealed Classes에 대해서는 JDK 15에 소개한 글을 참고하면 된다. 

2020.10.13 - [Study/Java] - JDK 15 New Features

JEP 410: Remove the Experimental AOT and JIT Compiler

실험적인 Java 기반 AOT(Ahead-of-Time) 및 JIT(Just-In-Time) compiler를 제거한다.
이 compiler는 도입 이후 거의 사용되지 않았으며 유지 관리에 필요한 노력이 상당하다.
개발자가 JIT compile을 위해 외부 빌드 버전의 compiler를 계속 사용할 수 있도록 실험적인 Java 수준 JVM compiler interface(JVMCI)는 유지한다.

JEP 411: Deprecate the Security Manager for Removal

향후 release에서 제거하기 위해 Security Manager를 더 이상 사용하지 않는다.
Security Manager는 Java 1.0부터 시작되었다.
수년 동안 client 측 java 코드를 보호하는 주요 수단이 아니었으며 서버 측 코드를 보호하는데 거의 사용되지 않았다.
Java를 발전시키기 위해 레거시 Applet API(JPE 398)와 함께 제거하기 위해 Security Manager를 더 이상 사용하지 않는다.

JEP 412: Foreign Function & Memory API (Incubator)

Java 프로그램이 Java runtime 외부의 코드 및 데이터와 상호 운용할 수 있는 API를 소개한다.
API는 외부 함수 (즉, JVM 외부 코드)를 효율적으로 호출하고 외부 메모리(즉, JVM에 의해 관리되지 않는 메모리)에 안전하게 액세스함으로써 Java 프로그램이 JNI의 취약성과 위험 없이 네이티브 라이브러리를 호출하고 네이티브 데이터를 처리할 수 있도록 한다.

JEP 415: Context-Specific Deserialization Filters

각 개별 deserialization operation에 대한 필터를 선택하기 위해 호출되는 JVM-wide filter factory를 통해 application에서 상황에 맞는 desilialization filter를 구성할 수 있다.

반응형
profile

파란하늘의 지식창고

@Bluesky_

내용이 유익했다면 광고 배너를 클릭 해주세요