JDK 11 ~ 17 Release, 제대로 이해하기

2023. 6. 8. 23:08Spring/Java

반응형

본 포스팅은 openJDK 11 부터 17 까지의 변경 사항을 확인하고, JDK 17을 사용할 때 개발자로서 알아두면 좋을 내용을 학습합니다.

 

 

Previous Series:

👉🏻 JDK 11 ~ 17 Release, 제대로 이해하기 (current)

📌 JDK 17 ~ 21 Release, 제대로 이해하기

 


 

Release Timeline

JDK 11는 JDK 17 바로 이전의 LTS release 입니다.

 

 

Oracle Java SE Product Releases Timeline

 

 

본 포스팅에서는 JDK 11 릴리즈 이후에 변화된 JDK Release를 시간 순서대로 다뤄볼 예정입니다.

 

 

 


 

 

JDK 12

JDK 12에서는 Compact Number Formatting을 지원하고, 확장된 Switch 표기법 처음 소개 (Preview) 했습니다.

새로운 모습의 Switch 문은 자세한 내용은 정식으로 릴리즈 되는 JDK 14에서 다루겠습니다.

 

모든 변화된 JDK 12 기능은 아래를 확인하세요.

 

 

 

✔️ Compact Number Formatting

Compact Number Formats

 

 

단축된 숫자 표기 형식을 위한 NumberFormat을 지원합니다.

가령 Locale을 en_US 로 설정한다면, 1000은 "1K"로, 1000000은"1M"로 Formatting 할 수 있습니다.

숫자 표기 형식은 NumberFormat.Style을 통해 설정할 수 있습니다.

 

// Local: US, Style: SHORT 
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.SHORT);
fmt.format(1_000);		// 1K
fmt.format(10_000);		// 10K
fmt.format(1_000_000);		// 1M

// Local: US, Style: LONG
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.US, NumberFormat.Style.LONG);
fmt.format(1_000);		// 1 thousand
fmt.format(10_000);		// 10 thousand
fmt.format(1_000_000);		// 1 million


// Local: KOREA, Style: SHORT
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.KOREA, NumberFormat.Style.SHORT);
fmt.format(1_000);		// 1천
fmt.format(10_000);		// 1만
fmt.format(1_000_000);		// 100만

 

 

 

 

 

✔️ Collectors.teeing()

 

Java 12에서 등장한 Collectors.teeing()은 static method로, 두 Collector의 수행 결과를 결합해 결과를 냅니다.

java.util.stream 패키지에 정의된 Collectors 클래스 내에 정의되어 있습니다.

 

//Import
import java.util.stream.Collectors;

// Syntax
public static <T, R1, R2, R> Collector<T, ?, R> teeing(
        Collector<? super T, ?, R1> downstream1, 	// ①
        Collector<? super T, ?, R2> downstream2, 	// ②
        BiFunction<? super R1, ? super R2, R> merger) 	// ③

 

Parameter

Collector<? super T, ?, R1> downstream1: 첫 번째 Collector
Collector<? super T, ?, R2> downstream2: 두 번째 Collector
BiFunction<? super R1,? super R2, R> merger: 첫 번째와 두 번째 Collector의 결과 값을 결합하기 위한 함수

 

List<Integer> grades = List.of(3, 5, 5, 2, 5, 3, 4, 3, 5, 4, 4, 2);

Double averageGrade = grades.stream()
    .collect(teeing(
        summingDouble(i -> i),
        counting(),
        (sum, n) -> sum / n));

System.out.println(averageGrade);	// 3.75

 

 

 


 

 

JDK 13

JDK 15에서 정식으로 릴리즈될 Text Block의 Preview가 소개되었습니다. 자세한 내용은 15에서 다루겠습니다.

JDK 13에서는 자세히 다룰만한 큰 변화는 없습니다.

 

 

 


 

JDK 14

Java의 새로운 타입인 Records가 Preview로 처음 소개되었습니다.

이전의 가장 최근 소개된 Java Type이 Java 5의 Enum이라는 것을 감안하면 꽤 주목할만한 내용이라는 것을 알 수 있습니다.

 

 

 

 

 

✔️ Switch expressions

📑 JEP 361: Switch Expressions

 

 

JDK 12와 JDK 13의 Preview 로 소개되었던, 새로운 Switch 구문이 JDK 14에 정식 릴리즈 되었습니다.

 

추가된 기능은,

첫 번째로 기존의 방식과 동시에 새로운 기능의 Switch 문을 사용할 수 있습니다. 

두 번째는 기존의 방식에 yield 키워드를 통해 결과 값을 산출하도록 만들 수 있습니다. 

 

하나씩 살펴보도록 하겠습니다.

 

- Arrow labels

Yielding a value

Exhaustiveness 

 

 

 

# Arrow labels

Traditional Case : case L: Label, fall through

New Case           :  case L -> Label, No fall-through

좌) Traditional Case,   우) New Case

 

새로운 구문은 위와 같이 화살표 (->)로 표시하며, 다중 조건으로 판단해 구문을 실행할 수 있습니다. 

 

 

Unnecessary break;

기존의 방식은 case 조건에 맞다면 해당 구문을 실행하고,

그 아래 case로 이동하는 fall-through 가 진행되었습니다.

 

때문에 필요한 곳에 break; 을 작성하여,

fall-through를 막는 형식으로 사용하기도 했습니다.

모든 case에 break 키워드를 붙여야 하는 번거러움 때문에 switch를 잘 사용하지 않기도 하죠.

 

뿐만 아니라, 위처럼 다중 조건 할당 (multi-way conditions)을 위해 break;를 작성하지 않고,

fall-through를 유도하여 break가 걸리기 전의 구문을 실행하도록 만들기도 했습니다.

 

예를 들어, MONDAY와 FRIDAY는 num에 6을 할당하기 위해,

SUNDAY의 구문이 걸릴 때까지 아무런 구문도 없이 조건 정의만 되어 있습니다.

 

새로운 구문에서는 불필요하도록 매 조건마다 달아야하는 break를 개선합니다.

 

 

num 변수 값 할당

또한 이전에는 num에 변수 값을 할당하기 위해 case 문 마다 변수 값 할당을 따로 지정해주어야 합니다.

JDK 14에서는 yield와 새로운 구문을 통해 switch에서 특정 값을 산출해낼 수 있습니다.

위에서도 num 값을 switch 구문으로 

 

 

 

#  Yielding a value

또한 새로운 switch 표현식 뿐만 아니라, 기존의 switch 문에서도 결과 값을 산출할 수 있게 되었습니다.

 

 

s 값에 따라 기존 방식인 case L: 에서 yield 키워드를 통해 산출 값을 반환하는 것을 확인할 수 있습니다.

 

 

 

# Exhaustiveness 

철저함

 

switch에 정의된 case는 반드시 모든 케이스를 정의내려야 합니다 (exhaustive). 

기존 방식의 Switch 문은 종종 놓치는 케이스를 염려하여 default를 정의내리곤 했습니다.

 

 

새로운 switch 문에서는 모든 케이스를 정의하도록 강제하며,

만약 빠진 구문이나 반환되는 값이 완전하지 않다면 컴파일 오류가 발생합니다.

 

 

 

 

 

 

✔️ Helpful NullPointerExceptions

📑 JEP 358: Helpful NullPointerExceptions

 

 

종종, 복잡한 구조에서 객체가 null 일 경우 디버깅에 어려움을 겪었습니다.

JDK 14부터는 NullPointerException의 자세한 설명을 통해 조금 더 쉽게 디버깅할 수 있도록 합니다.

 

 

public static void main(String[] args) {
    HashMap<String, GrapeClass> grapes = new HashMap<>();
    grapes.put("grape1", new GrapeClass(Color.BLUE, 2));
    grapes.put("grape3", null);
    grapes.get("grape3").getColor(); // ERROR! NullPointException
}

 

이전 방식에서는 아래와 같이 출력이 되었다면, 

 

Exception in thread "main" java.lang.NullPointerException
        at com.mydeveloperplanet.myjava17planet.HelpfulNullPointerExceptions.main(HelpfulNullPointerExceptions.java:13)

 

변경된 방식은 아래와 같습니다.

 

Exception in thread "main" java.lang.NullPointerException: 
	Cannot invoke "...GrapeClass.getColor()" because the return value of "java.util.HashMap.get(Object)" is null
    at ....HelpfulNullPointerExceptions.main(HelpfulNullPointerExceptions.java:13)

 

Null 값의 위치를 자세히 명시해줍니다.

간단한 예시로 a.b.c.i = 99; 에서 오류가 발생한다면, 어디에서 오류가 발생했는지 아래와 같이 명시해줍니다.

 

Exception in thread "main" java.lang.NullPointerException: 
        Cannot read field "c" because "a.b" is null
    at Prog.main(Prog.java:5)

 

 

 


 

 

JDK 15

자바 15 릴리스 기능은 공식적으로 text blocks 기능을 포함하고 있으며,

ZGC와 Shenandoah 가비지 컬렉터에 대한 개선을 지속하고 있습니다. 

 

 

 

 

 

✔️ Text Blocks

📑 JEP 378: Text Blocks

 

Java SE 13에서 등장했던 Text Block 기능이 preview 기능에서 permanent 기능으로 변경되었습니다.

Text Block은 대부분의 이스케이프 시퀀스가 필요하지 않고, 자동으로 문자열 형식을 예측 가능한 방식으로 지정하며, 원하는 경우 개발자가 형식을 제어할 수 있도록 하는 여러 줄 문자열 리터럴입니다.

 

 

Goals
문자열 사이에 사용하던 Escape Sequence(Escape 특수 문자)의 사용을 줄이면서,

+ 기호 없이도 여러 줄의 문자열을 단순하게 표현할 수 있도록 합니다.

 

텍스트 블록은 세 개의 큰따옴표로 정의되며, 시작을 표시하는 세 개의 큰따옴표와 끝을 표시하는 세 개의 큰따옴표는 같은 줄이면 Compile Error가 발생하며, 반드시 한 줄을 띄어 표기합니다.

 

 

 

기본적으로 시작과 끝의 큰따옴표의 위치에 따라 공백이 달라집니다.

 

마지막 라인은 자동으로 줄바꿈이 됩니다.

 

 

 

마지막 라인 줄바꿈을 막으려면 아래와 같이 마지막 문자열 끝에 종단 큰 따옴표를 붙이면 됩니다.

 

 

 

 

 

# New string methods

text blocks과 함께 아래와 같은 메소드들이 함께 소개되었습니다.

 

stripIndent() : 불필요한 공백을 제거합니다.

translateEscapes() : Escape 문자를 변환한 새로운 문자열을 반환합니다.

formatted() : 기존의 String Format 과 같이 템플릿 형식으로 변환한 값을 반환합니다.

 

System.out.println("%s\\n%s\\n%s"
	.translateEscapes()			// translateEscapes: \\n -> (new line)
	.formatted(				// formatted: %s -> string
		"   hello world".stripIndent(),	// stripIndent: remove unnecessary space
		"hello world  ".stripIndent(),
		"   hello world  ".stripIndent()));

 

 

출력값은 아래와 같습니다.

hello world
hello world
hello world

 

 

 

✔️ Z Garbage Collector

experimental 기능이었던 Z Garbage Collector(ZGC)가 프로덕션에서 사용할 수 있게 되었습니다.

-XX+ZGC : command 옵션 -XX를 사용하여 ZGC를 활성화합니다. 

 

ZGC는 높은 성능을 보이는 새로운 Garbage Colloctor로, Garbage Colloctor의 키워드를 뽑자면 다음과 같습니다.

 

- Concurrent

- Region-based

- Compacting

- Colored pointers

- Load barriers 

 

더 자세한 내용은 JDK 공식 문서를 확인하시길 바랍니다.

 

 

 


 

 

JDK 16

JDK 16에서는 데이터 record 클래스가 정식 출시되었고,  instanceof 의 Pattern Matching 이 더욱 사용하기 편리한 형식으로 개선되었습니다.

 

 

 

 

 

✔️ Record Class

📑 JEP 395: Records

 

JDK 16에서 출시된 가장 큰 기능은 record 클래스입니다.

record 클래스는 data-carrier classes, 데이터 객체의 역할을 합니다.
덕분에, Immutable Java Class 를 표현할 때의 Boilerplate 코드를 줄여줍니다.

 

아래의 record 클래스를 확인해볼게요.

위 클래스는 아래 클래스와 동일합니다.

 

몇 가지 record class의 규칙을 적어보자면 아래와 같습니다.

 

- record class는 암묵적으로 final class이며, 추상 클래스 abstract class가 될 수 없습니다.
- record class 내의 필드는 final입니다. data-carrier classes로써 Immutable 객체를 목표로 합니다.

- 접근자, 생성자, equals, hashCode 및 toString 메서드가 자동으로 생성됩니다. 

 

접근자는 getter와 동일한 역할로, 필드 명을 그대로 가진 메서드 접근자를 생성합니다.

 

 

IntelliJ에서 생성한 record에 접근할 수 있는 메소드를 확인해보면 위와 같습니다.

 

record 클래스와 관련한 내용은 다룰 것이 많지만, 본 글에 다루기에는 적합하지 않아

위의 JEP 공식 문서를 참고하시는 것을 추천드립니다.

 

 

 

 

 

 

 

✔️ Pattern Matching for instanceof

📑 JEP 394: Pattern Matching for instanceof

 

instanceof 연산자의 패턴 매칭으로 간결한 분기 처리를 할 수 있습니다.

간단한 예로, 아래와 같은 일반적인 패턴의 코드를

 

if (obj instanceof String) { String s = (String) obj; // grr... ... }

 

다음과 같이 바꿔 사용할 수 있습니다. 

 

if (obj instanceof String s) { // Let pattern matching do the work! ... }

 

String sif 문 내에서 사용할 수 있습니다.

 

if (obj instanceof String s && s.length() > 5) {
    flag = s.contains("jdk");
}

 

 

 

 

✔️ Stream.toList()

Steam에서 List로 변환시키기 위해 사용하던 Collector.toList() 대신, stream 에서 바로 ImmutableList로 변경하여 반환하는 메소드가 추가되었습니다. 

 

 

좌측은 ArrayList를 생성하는 JDK 16 이전에 사용하던 stream → List 변환 방식이며, 

우측은 ImmutableCollection을 생성하는 JDK 16 이후의 stream → List 변환 방식입니다.

 

 

 


 

 

JDK 17

LTS release features And finally, here are the enhancements made to the latest, Java LTS release:

 

 

 

 

✔️ Sealed Class

📑 JEP 409: Sealed Classes

 

seal
verb. fasten or close securely

 

sealed class 는 이름 그대로 클래스를 안전을 위해 감추는 기능을 합니다.

sealed 키워드로 클래스를 정의하면 permits 키워드 이후의 명시된 클래스 이외에는 확장할 수 없습니다.

즉, 어떤 클래스가 해당클래스를 상속받는지를 쉽게 알수 있고 제한할수 있습니다.

 

 

 

가령, 위의 예시에서는 Circle, Square, Rectangle 이외의 클래스는 Shape 객체를 확장할 수 없습니다.

sealed 클래스의 하위 클래스반드시 아래 키워드 중 하나를 명시해야만 합니다.

 

 

  • sealed – ① 허용된 (permitted) 하위 클래스만 확장을 허용하거나, ② 정의된 파일 내의 class만 확장 가능하도록 정의
  • non-sealed – 확장에 열린 상태로 정의
  • final – 확장에 닫힌 상태로 정의

 

sealed 클래스를 확장하는 하위 클래스는 동일한 모듈이나 동일한 패키지에서 정의할 수 있습니다.

위의 Shape 클래스를 확장하는 Circle, Square, Rectangle는 아래와 같이 정의할 수 있습니다.

 

 

혹은 아래와 같이 동일한 파일 내에 정의하여 permits 구문을 생략할 수 있습니다.

 

 

 

전체적인 구조를 도식화하여 확인해보면 아래와 같습니다.

 

https://www.happycoders.eu/java/sealed-classes/

 

 

 

 

 

 

 

 

 

 

Reference

- theserverside: all-changes-in-Java-17-since-JDK-11-LTS-release

- docs.oracle.com/en/java/javase/17/language/

- openjdk.org/projects/jdk/17

- openjdk.org/projects/jdk/17/jeps-since-jdk-11

 

반응형