Java/Version / / 2023. 5. 4. 13:50

Java9 ~ Java17

버전 8 출시 이후 버전 18까지 Java는 203개의 JDK Enhancement Propsals(JEP) 와 더 많은 소규모 업데이트로 이루어져 있으며, 각 업데이트는 플랫폼에 약간의 개선을 가져옵니다. 이 페이지는 가장 중요한 개선 사항을 분류하고, 선별한 목록입니다.

* Pattern Matching for switch

-- JDK18, JDK17 프리뷰

switch 분기는 매우 제한적이었습니다. case 구문은 정확한 동등성(==)만 테스트 할 수 있었고, 숫자, Enum, String 타입만 가능했습니다.
이제는 이에 더해 어떠한 타입과 복잡한 패턴에 대해서도 switch 구문이 작동합니다.

- 전통적인 Enum 에 의한 분기

var symbol = switch (expression) {
  case ADDITION       -> "+";
  case SUBTRACTION    -> "-";
  case MULTIPLICATION -> "*";
  case DIVISION       -> "/";
};

- 향상된 표현식

String formatted = switch (o) {
    case Integer i && i > 10 -> String.format("a large Integer %d", i);
    case Integer i           -> String.format("a small Integer %d", i);
    default                  -> "something else";
};

- null 값 지원

switch (s) {
  case null  -> System.out.println("Null");
  case "Foo" -> System.out.println("Foo");
  default    -> System.out.println("Something else");
}

- 반환값 표현

public String printDay(Day today) {
    String result = switch (today) {
        case MON, TUE, WED, THUR, FRI -> today.name() + " is Weekday";
        case SAT, SUN -> today.name() + " is Weekend";
    };
    return result;
}

- 반환값 블록

public String printDay(Day today) {
    String result = switch (today) {
        case MON, TUE, WED, THUR, FRI -> today.name() + " is Weekday";
        case SAT, SUN -> {
            System.out.print("Holiday! ");
            yield today.name() + " is Weekend";
        }
    };
    return result;
}

- 구체적인 case 문 선행

switch(o) {
    case -1, 1 -> "special case"
    case Integer i && i > 0 -> "positive number"
    case Integer i -> "other integer";
}

- Sealed Class

sealed interface I<T> permits A, B {}
final class A<X> implements I<String> {}
final class B<Y> implements I<Y> {}

static int testGenericSealedExhaustive(I<Integer> i) {
    return switch (i) {
        // Exhaustive as no A case possible!
        case B<Integer> bi -> 42;
    }
}

 

* 봉인된 클래스

-- JDK17 (JDK16, JDK15프리뷰)

봉인된 클래스는 허용되는 클래스를 한정하여 직접 지정하는 것을 뜻합니다.
이 경우 허용된 하위클래스의 봉인계층을 명시적으로 정의해야 합니다.

public sealed class Shape
    permits Circle, Quadrilateral, WeirdShape {...}

public final class Circle extends Shape {...}

public sealed class Quadrilateral extends Shape
    permits Rectangle, Parallelogram {...}
public final class Rectangle extends Quadrilateral {...}
public final class Parallelogram extends Quadrilateral {...}

public non-sealed class WeirdShape extends Shape {...}

switch 구문과 더불어 sealed 클래스는 Enum의 좋은 대안이 될 때가 많습니다.

- Record Class

C# 의 Struct Class 와 매우 유사합니다. private field, gettter, setter, 생성자를 사용하는 일반적인 클래스에 비해 간단한 구문을 사용하여 생성할 수 있습니다.

public record Point(int x, int y) { }

위의 레코드 클래스는 다음을 정의하는 일반 클래스와 매우 유사합니다 .

  • 두 개의 private final필드 int x및int y
  • x, y 를 매개변수로 받는 생성자
  • x, y 필드에 대한 게터 역할을 하는 메소드 x(), y()
  • x, y 를 고려하여 hashCode, equals및 toString 메소드 생성

아래처럼, 일반 클래스처럼 사용할 수 있습니다.

var point = new Point(1, 2);
point.x(); // returns 1
point.y(); // returns 2

Tip) Record class를 이용하여 중간 변환 모델링(DTO)을 쉽게 구현할 수 있습니다.

public List<Product> findProductsWithMostSaving(List<Product> products) {
  record ProductWithSaving(Product product, double savingInEur) {}

  products.stream()
    .map(p -> new ProductWithSaving(p, p.basePriceInEur * p.discountPercentage))
    .sorted((p1, p2) -> Double.compare(p2.savingInEur, p1.savingInEur))
    .map(ProductWithSaving::product)
    .limit(5)
    .collect(Collectors.toList());
}

 

* instanceof 패턴 일치

-- JDK16 (JDK15, JDK14 프리뷰)

instanceof 연산자 사용 시 직접 캐스팅이 가능하고, 바로 사용가능합니다.
아래의 코드는 이러한 복잡한 경우의 사용 예입니다.

if (obj instanceof String s && s.length() > 5) {
  // use s
}

 

* 텍스트 블록

-- JDK15 (JDK14, JDK13 프리뷰)

다른 모던 언어에서 제공하는 '여러 줄' 텍스틀 표현하기 위한 텍스트 블록이 지원됩니다.

String html = "";
html += "<html>\n";
html += "  <body>\n";
html += "    <p>Hello, world</p>\n";
html += "  </body>\n";
html += "</html>\n";

System.out.println(html);

위 예제에서 아쉽게도 보간문자(interpolation) 은 아직 지원되지 않습니다. 불편하지만, 해당 기능이 지원될 때까지는 String::formatted, String::format 를 사용할 수 밖에 없습니다.

var greeting = """
    hello
    %s
    """.formatted("world");

 

* NullPointerExceptions

-- JDK15 (JDK14 프리뷰)

아래의 경우에 어떤 메소드가 null을 반환하는지 명확하지 않았습니다. 이러한 이유로 많은 개발자는 어떤 단계에서 예외가 발생했는지 파악하기 위해 여러줄에 걸쳐 나눠서 코드를 작성하였습니다.

node.getElementsByTagName("name").item(0).getChildNodes().item(0).getNodeValue();

Exception in thread "main" java.lang.NullPointerException
        at Unlucky.method(Unlucky.java:83)
  • 위의 경우에 JDK15 이후부터는 Exception 메시지가 아래와 같이 나옵니다.
    Exception in thread "main" java.lang.NullPointerException:
  • Cannot invoke "org.w3c.dom.Node.getChildNodes()" because the return value of "org.w3c.dom.NodeList.item(int)" is null at Unlucky.method(Unlucky.java:83)

 

* 표현식 전환

-- JDK14 (JDK13, JDK12 프리뷰)

Java14 에서는 기존의 switch기능이 개선되었습니다. Java는 이전 switch 문을 계속 지원하지만 언어에 새로운 switch 표현식을 추가합니다.

int numLetters = switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> 6;
    case TUESDAY                -> 7;
    default      -> {
        String s = day.toString();
        int result = s.length();
        yield result;
    }
};

switch 표현식의 경우 break 문이 더 이상 필요 없으므로, 위의 예제는 누락으로 인한 버그가 발생하지 않습니다.

 

* 지역변수 타입 추론

-- JDK11

var greetingMessage = "Hello!";

대부분의 모던 언어에서 제공되는 기능입니다. 적절한 사용예 및 주의 점은 다른 언어에서의 관례와 비슷합니다.

함수 내 제한된 스코프내에서 사용하길 권합니다. 특히나 복잡한 제네릭 구문이 간소화 되어서 코드를 보기가 편해집니다. 다만 너무 넓은 스코프에서 'var' 키워드를 쓰면 타입을 유추하기가 어려워집니다.

  • 네이버 블로그 공유
  • 네이버 밴드 공유
  • 페이스북 공유