[JAVA] JDK 17에서 제공하는 새로운 기능들 정리해보기

kindof

·

2022. 11. 18. 13:57

1. 빨라진 JDK Release

작년 9월, JDK 17이 최초로 공개되었고 바로 얼마 전에는 JDK 19 버전이 출시되었습니다.

 

비록 JDK 19가 LTS 버전은 아니지만, 내년 하반기에는 JDK 21 LTS 버전이 출시된다고 하는데요.

 

과거에는 JDK 버전업과 LTS 버전의 출시가 굉장히 큰 간격을 두고 이루어졌지만, 최근 들어 자바 진영에서는 새로운 JDK 버전의 Release를 공격적으로 가져가고 있습니다.

https://dev.java/download/releases/

이렇게 변화의 속도가 빨라진 요즘, JDK에서 제공하는 기능들은 무엇이 있는지 이해하고 필요에 따라 적절히 사용하는 것은 기술 부채의 관점에서 개인이나 팀에게 굉장히 중요한 요소라고 생각합니다.

 

그래서 이번 글에서는 JDK 17 LTS 버전에서 제공하는 기능 몇 가지에 대한 예제 코드를 작성하면서 정리해보는 시간을 가져보겠습니다.

 

 

2. JDK 17 Features

Switch문 Pattern Matching / Guarded Pattern

public class SwitchDemo {

    public static class Shape {
        final int numberOfSide;
        int size;

        public Shape(int numberOfSide, int size) {
            this.numberOfSide = numberOfSide;
            this.size = size;
        }
    }

    public static class Triangle extends Shape{
        public Triangle(int size){
            super(3, size);
        }
    }

    public static class Rectangle extends Shape {
        public Rectangle(int size){
            super(4, size);
        }
    }
}

Shape 클래스가 있고 Triangle과 Rectangle 클래스는 Shape를 상속하고 있습니다. 이런 상속 구조는 OOP에서 굉장히 자주 사용되죠.

 

 

이 때 '특정 클래스가 어떤 타입인가'에 따라 다른 비즈니스 로직 적용이 필요한 상황 역시 자주 찾아오게 되고, 이를 위해 과거에는 조건문이나 해당 클래스의 필드값 중 하나를 Getter로 조회하여 Switch 문에 적용시키는 식으로 처리하곤 했습니다.

// [1] If 조건문
if(shape instanceof Triangle){
    // Triangle에 대한 로직
} else if(shape instanceof Rectangle){
    // Rectangle에 대한 로직
}
...

########################################################

// [2] 특정 필드값으로 Switch문(ENUM)
String type = shape.getType();
switch(type){
    case 'TRIANGLE' :
    	// 비즈니스 로직
        break;
    case 'RECTANGLE' :
    	// 비즈니스 로직
        break;
    ...
}

 

하지만 JDK 17(Preview)에서는 이제 객체의 타입에 따른 Switch문(Pattern Matching)을 제공합니다.

 /*
 * you can pass objects in switch condition and this object can be checked for different types in switch case labels.
 * Guarded Patterns
 */
public void checkShape(Shape shape) {
    switch (shape) {
        case Triangle t && (t.size > 500) -> System.out.println("This is very big Triangle");
        case Triangle t -> System.out.println("This is Triangle");
        case Rectangle r -> System.out.println("This is Rectangle");
        default -> System.out.println("No shape matched");
    };
}

case Triangle t -> System.out.println("This is Triangle"); 부분을 보면 shape 자체가 어떤 클래스의 instance 인지를 체크하고 있죠. 이를 통해 더 간결한 코드를 짤 수 있게 되었습니다. - Pattern Matching

 

뿐만 아니라, Triangle t && (t.size > 500) 과 같은 조건문의 내용을 case문 안에 추가하여 처리함으로써 불필요한 if문의 사용을 줄일 수 있게 되었습니다. - Guarded Pattern

 

Switch문 테스트

 

구체적인 NullPointerException 

아래 테스트 코드는 NullPointerException을 발생하게 하고, 이에 대한 메시지를 출력하고 있습니다.

NPE Message가 구체적이다.

이 때, 사진에서 빨간색 박스로 표시한 부분이 JDK 17에서 제공하는 NPE의 구체적인 예외 메시지인데요.

어느 포인트에서 NPE가 발생했는지에 대한 정보를 함께 제공함으로써 예외를 Tracking하는 데 도움을 주고 있습니다.

 

아래는 JDK 8로 버전을 낮춘 뒤, 똑같은 테스트 코드를 수행한 결과입니다.

NPE Message도 null

NPE에 대한 어떠한 정보도 제공하지 않습니다. 물론 예외의 Stacktrace는 보여주겠지만, 코드의 어떤 부분인지 알려주지는 않습니다.

 

 

collect(Collectors.toList()) 대신에 toList()

JDK 8 이후에는 stream() 을 사용하고 마지막에 결과를 List로 변환할 때 .collect(Collectors.toList())를 주로 사용해왔습니다.

 

하지만 JDK 17 부터는 .toList()만으로 결과 리스트를 반환할 수 있게 되었습니다.

collect(Collectors.toList()) -> toList()

 

그런데 여기서 한 가지 더 기억해야 할 포인트는 .collect(Collectors.toList())와 .toList()의 결과값의 성질이 다르다는 것인데요.

.collect(Collectors.toList())는 수정 가능한 리스트를 반환하는 반면, .toList()는 수정이 불가능한 리스트를 반환합니다.

.collect(Collectors.toList()) -> 수정 가능
toList() -> 수정 불가능

 

 

Sealed Interface/Class

Sealed 인터페이스는 자신을 Extends할 수 있는 인터페이스를 '제한할 수 있는' 특성을 갖습니다.

Sealed Interface

위 예시를 보면 SchoolRule 인터페이스는 isGraduated(), isTardy() 메서드를 가지고 있으며 sealed, permits 키워드를 통해 자신을 extends할 수 있는 인터페이스를 ClassRoomRule 인터페이스로 한정하고 있습니다.

 

그래서 MyRule이라는 인터페이스가 SchoolRule 인터페이스를 Extends 하려고 하면, 컴파일 에러가 나는 것을 볼 수 있습니다.

 

즉, sealed 인터페이스는 개발자로 하여금 특정 인터페이스의 사용처를 명확히 한정함으로써 설계의 안정성을 보장할 수 있도록 하는 것입니다.

 

 

Sealed 클래스 역시 비슷한 성질을 갖습니다.

Sealed Class

School 이라는 클래스는 Teacher, Student 두 클래스만 자신을 상속할 수 있도록 합니다. 즉, 자신을 상속할 수 있는 클래스를 미리 permits로 한정해둠으로써 무분별하게 상속되어 의도와 다르게 사용되는 상황을 방지하는 것이죠.

 

그래서 Parent라는 클래스가 School을 상속하려고 하면 위와 같이 컴파일 에러가 납니다. 예시가 조금 어색하긴 하지만...

 

3. 정리

지금까지 JDK 17이 제공하는 기능 중에서 제가 관심을 갖고 본 몇 가지 기능들을 정리해봤습니다.

JDK 버전이 올라가면서 느끼는 점 중 하나는 "기능의 확장과 기능의 제한이 동시에 진행되고 있다는 것"인데요.

 

개발자가 조금 더 편하게 개발하기 위해 기능을 확장하면서도, 의도치 않은 사용을 제한하기 위해서나 조금 더 클린한 OOP 패러다임을 구축하기 위해 기능의 사용을 '구체화'하여 제한하는 것 같다는 개인적인 생각입니다.

 

어쨌든, 오늘 정리한 내용을 그냥 이론으로만 알고 있지 말고 JDK 17이 적용된 프로젝트들에서 꾸준히 사용하는 것이 중요할 것 같습니다.

 

3. Reference

- https://dev.java/download/releases/

- https://velog.io/@cieroyou/Stream%EC%9D%84-List%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%98%EB%8A%94-%EB%8B%A4%EC%96%91%ED%95%9C-%EB%B0%A9%EB%B2%95%EA%B3%BC-%EC%B0%A8%EC%9D%B4Collectors.toList-vs-Stream.toList