파란하늘의 지식창고
Published 2022. 9. 21. 19:07
JDK 19 New Features Study/Java
반응형

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


Spec

Java SE 19 Platform JSR 394에 정의된 바와 같이 JSR 394 구현이 목표
실제 Spec은 Final Release Specification 문서를 참고해야 함

Final Release Specification Feature Summary

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

JEP Component Feature
JEP 405 specification/language Record Patterns (Preview)
JEP 422 hotspot/compiler Linux/RISC-V port
JEP 424 core-libs Foreign Function & Memory API (Preview)
JEP 425 core-libs Virtual Threads (Preview)
JEP 426 core-libs Vector API (Forth Incubator)
JEP 427 specification/language Pattern Matching for switch (Thrid Preview)
JEP 428 core-libs Structured Concurrency (Incubator)

JEP 405: Record Patterns (Preview)

JEP 405: Record Patterns (Preview)는 JEP 427: Pattern Matching for switch (Thrid Preview)와 연관되어 있다.
JDK 16에 추가된 JEP 395: Records를 통해 record에 대한 instanceof pattern matching으로 다음과 같은 처리가 가능해졌었다.

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

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

record에 대해서도 pattern matching을 처리할 수 있었다.

record Point(int x, int y) {}

static void printSum(Object o) {
    if (o instanceof Point p) {
        int x = p.x();
        int y = p.y();
        System.out.println(x+y);
    }
}

record의 경우 생성자의 매개변수가 record의 멤버 변수가 되기 때문에 생성자를 바로 사용할 수 있도록 다음과 같은 처리가 가능해지게 된다.

record Point(int x, int y) {}

void printSum(Object o) {
    if (o instanceof Point(int x, int y)) {
        System.out.println(x+y);
    }
}

생성자에 대한 pattern matching은 중첩 레코드를 사용할 때 우발적 복잡성을 제거하여 해당 Object가 표현하는 데이터에 집중할 수 있도록 한다.

record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}

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);
    }
}

Record patterns

record class가 generic인 경우 이에 대해 instanceof에서 해당 generic을 사용한 매개변수를 인지할 수 없었다.
이제 다음과 같이 사용할 수 있다.

record Box<T>(T t) {}

static void test1(Box<Object> bo) {
    if (bo instanceof Box<Object>(String s)) {
        System.out.println("String " + s);
    }
}
static void test2(Box<Object> bo) {
    if (bo instanceof Box<String>(var s)) {
        System.out.println("String " + s);
    }
}

Generic 명시 부분이나 매개 변수 둘 중 하나라도 타입 추론이 가능해야 한다.
만약 아래와 같은 경우는 에러가 발생한다.

static void erroneousTest1(Box<Object> bo) {
    if (bo instanceof Box(var s)) {                 // Error
        System.out.println("I'm a box");
    }
}
static void erroneousTest2(Box b) {
    if (b instanceof Box(var t)) {                  // Error
        System.out.println("I'm a box");
    }
}

Record patterns and exhaustive switch

지난 JDK 18에선 JEP 420: Pattern Matching for switch (Second Preview)을 통해 permit나 extend, implement에 대한 pattern matching을 향상했었다.
Record에 대해서도 이의 연장선으로 switch에 대한 Pattern Matching이 이루어진다.

class A {}
class B extends A {}
sealed interface I permits C, D {}
final class C implements I {}
final class D implements I {}
record Pair<T>(T x, T y) {}

Pair<A> p1;
Pair<I> p2;

위와 같이 extended class와 implements class가 있고 관련 선언을 Generic으로 사용한 record Pair<T>(T x, T y) {} 가 있다.
extends Class를 Generic으로 사용한 경우는 일치한 클래스에 대해서 체크하기 때문에 아래의 경우 에러가 발생한다.

switch (p1) {                 // Error!
    case Pair<A>(A a, B b) -> ...
    case Pair<A>(B b, A a) -> ...
}

하지만 이 경우도 case에서 두 가지 유형의 값을 모두 포함하는 쌍에 대한 일치 항목이 없다면 에러가 발생하게 된다.
(아래의 경우 D, D의 경우가 없음)

switch (p2) {                        // Error!
    case Pair<I>(C fst, D snd) -> ...
    case Pair<I>(D fst, C snd) -> ...
    case Pair<I>(I fst, C snd) -> ...
}

JEP 424: Foreign Function & Memory API (Preview)

기존 JEP 412: Foreign Function & Memory API (Incubator), JEP 419: Foreign Function & Memory API (Second Incubator) 를 거쳐 Preview 기능이 되었다.
JNI를 대체하는 기능이라고 하는 데 사용하는 경우라면 살펴보면 좋을 듯하다..

JEP 427: Pattern Matching for switch (Third Preview)

Java 16에 적용된 JEP 394: Pattern Matching for instanceof를 통해 아래와 같이 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를 통해 반환이 가능해졌다.

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();
    };
}

하지만 case와 함께 조건을 체크하게 되는 경우 case의 반환 절에 조건문이 추가되어 다음과 같은 복잡한 코드로 이어질 수 있다.

static void test(Object o) {
    switch (o) {
        case String s:
            if (s.length() == 1) { ... }
            else { ... }
            break;
        ...
    }
}

이에 대한 개선으로 case when 구문을 지원하게 되었다.

static void test(Object o) {
    switch (o) {
        case String s when s.length() == 1 -> ...
        case String s                      -> ...
        ...
    }
}

다만 이렇게 when 구문을 지원하게 되면서 좀 더 엄격한 조건 적용이 필요하게 된다.
JEP 405에 설명되었듯이 모든 조건에 해당하는 switch 구문을 지정해야 하며 하위 조건을 포함하는 상위 조건이 case 조건 순서 위에 위치한 경우 하위 조건은 dead code가 되는 점, Interface와 Abstract 확장한 class에 대한 case 체크 시의 오류 발생 등은 유의해야 한다. (JEP 405의 설명과 동일하여 여기서는 생략함)

반응형
profile

파란하늘의 지식창고

@Bluesky_

도움이 되었다면 광고를 클릭해주세요