버전 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' 키워드를 쓰면 타입을 유추하기가 어려워집니다.