article thumbnail image
Published 2021. 10. 25. 23:33

 

 

 

9월 14일 오라클은 Java 17을 릴리즈했다.

Java 8, 11에 이은 LTS(Long-Term Support) 버전으로 오라클은 2년마다 LTS 버전을 릴리즈하고 있다.

이번 토이 프로젝트에 사용하기 위해 몇 가지 변경 사항을 정리해보려고 한다.

 

참고1)

non-LTS 버전들은 다음 버전이 릴리즈되면 지원이 종료된다.

예를 들어, Java 13이 릴리즈되면 Java 12에 대한 지원이 종료되는 것이다.

 

참고2) https://zdnet.co.kr/view/?no=20181102140004

오라클은 특정시점 이후부터 공식 기술지원을 종료한다.

이를 ‘엔드오브퍼블릭업데이트(EOPU)’라 부른다. 

EOPU 이후 JDK 버전별 패치는 유상 유지보수 계약을 체결한 상용 라이선스 보유자에게만 제공된다.

 

 

 

 

결론부터 이야기하면 Java 17에서는 크게 달라진 것은 없고 기존의 기능들이 개선되었다.

아래 사이트에서 자바 11 이후에 개선된 기능들을 번역해보았다.

 

https://dzone.com/articles/whats-new-between-java-11-and-java-17

 

 

텍스트 블록

 

private static void oldStyle() {
    String text = "{\n" +
                  "  \"name\": \"John Doe\",\n" +
                  "  \"age\": 45,\n" +
                  "  \"address\": \"Doe Street, 23, Java Town\"\n" +
                  "}";
    System.out.println(text);
}

 

기존에 JSON 문자열을 직접 생성할 때

이스케이프 처리로 가독성이 떨어지던 문제를 텍스트 블록을 통해 개선할 수 있다.

 

텍스트 블록은 3개의 큰 따옴표로 정의되며, 아래와 같이 작성할 수 있다.

 

private static void jsonBlock() {
    String text = """
            {
              "name": "John Doe",
              "age": 45,
              "address": "Doe Street, 23, Java Town"
            }
            """;
    System.out.println(text);
}

 

추가로, 텍스트 블록의 끝에 있는 세 개의 큰따옴표의 위치가 텍스트 블록의 시작 위치를 나타낸다.

 

private static void jsonMovedBracketsBlock() {
    String text = """
              {
                "name": "John Doe",
                "age": 45,
                "address": "Doe Street, 23, Java Town"
              }
            """; // 이 라인 space 공백 2개 제거하여 왼쪽으로 이동
    System.out.println(text);
}

 

이렇게 마지막 큰 따옴표 세개를 왼쪽으로 이동하면 각 줄 앞에 2개의 공백을 출력하게 된다.

 

  { // 공백 2개
    "name": "John Doe",
    "age": 45,
    "address": "Doe Street, 23, Java Town"
  }

 

private static void jsonMovedEndQuoteBlock() {
    String text = """
              {
                "name": "John Doe",
                "age": 45,
                "address": "Doe Street, 23, Java Town"
              }
                   """;
    System.out.println(text);
}

 

마지막으로, 위와 같이 큰 따옴표가 더 오른쪽으로 이동하게 되면

텍스트 블록의 시작이 첫 번째 문자에 의해 결정된다.

 

{ // 공백 없음
  "name": "John Doe",
  "age": 45,
  "address": "Doe Street, 23, Java Town"
}

 

 

 

Switch 표현식

 

주어진 열거형 값에 따라 작업을 수행해야 하는 경우 (여기서는 Fruit)

스위치 표현식을 사용하여 스위치에서 값을 반환하고 할당 등에서 값을 사용할 수 있다.

참고) https://congcoding.tistory.com/73

 

private static void oldStyleWithBreak(Fruit fruit) {
    switch (fruit) {
        case APPLE, PEAR: 
        // multivalue labels, and are indeed not supported prior to Java 14
            System.out.println("Common fruit");
            break;
        case ORANGE, AVOCADO:
            System.out.println("Exotic fruit");
            break;
        default:
            System.out.println("Undefined fruit");
    }
}

 

기존의 break 문은 누락, 가독성 문제가 있었는데 이를 아래와 같이 변경했다.

 

private static void withSwitchExpression(Fruit fruit) {
    switch (fruit) {
        case APPLE, PEAR -> System.out.println("Common fruit");
        case ORANGE, AVOCADO -> System.out.println("Exotic fruit");
        default -> System.out.println("Undefined fruit");
    }
}

 

반환값을 사용해야하면 아래와 같이 사용할 수 있다.

 

private static void withReturnValue(Fruit fruit) {
    String text = switch (fruit) {
        case APPLE, PEAR -> "Common fruit";
        case ORANGE, AVOCADO -> "Exotic fruit";
        default -> "Undefined fruit";
    };
    System.out.println(text);
}

 

하나 이상의 동작을 수행해야 할 때는 아래와 같이 사용한다.

 

private static void withYield(Fruit fruit) {
    String text = switch (fruit) {
        case APPLE, PEAR -> {
            System.out.println("the given fruit was: " + fruit);
            yield "Common fruit"; // 예약어 사용
        }
        case ORANGE, AVOCADO -> "Exotic fruit";
        default -> "Undefined fruit";
    };
    System.out.println(text);
}

 

 

Records

참고) Java Record

https://velog.io/@gilchris/Java-Record

 

 

롬복의 여러 기능을 Record를 사용하여 구현할 수 있다.

코드를 간결하게 만들기 위해 사용하던 롬복 버전에 종속성이 생기는 경우가 많은데 이를 개선했다.

 

public class GrapeClass {
 
    private final Color color;
    private final int nbrOfPits;
 
    public GrapeClass(Color color, int nbrOfPits) {
        this.color = color;
        this.nbrOfPits = nbrOfPits;
    }
 
    public Color getColor() {
        return color;
    }
 
    public int getNbrOfPits() {
        return nbrOfPits;
    }
 
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        GrapeClass that = (GrapeClass) o;
        return nbrOfPits == that.nbrOfPits && color.equals(that.color);
    }
 
    @Override
    public int hashCode() {
        return Objects.hash(color, nbrOfPits);
    }
 
    @Override
    public String toString() {
        return "GrapeClass{" +
                "color=" + color +
                ", nbrOfPits=" + nbrOfPits +
                '}';
    }
 
}

 

기존에 롬복을 사용하지 않고 인스턴스를 비교하기 위해 사용했던 코드들 

 

private static void oldStyle() {
    GrapeClass grape1 = new GrapeClass(Color.BLUE, 1);
    GrapeClass grape2 = new GrapeClass(Color.WHITE, 2);
    System.out.println("Grape 1 is " + grape1);
    System.out.println("Grape 2 is " + grape2);
    System.out.println("Grape 1 equals grape 2? " + grape1.equals(grape2));
    GrapeClass grape1Copy = new GrapeClass(grape1.getColor(), grape1.getNbrOfPits());
    System.out.println("Grape 1 equals its copy? " + grape1.equals(grape1Copy));
}
[Output]

Grape 1 is GrapeClass{color=java.awt.Color[r=0,g=0,b=255], nbrOfPits=1}
Grape 2 is GrapeClass{color=java.awt.Color[r=255,g=255,b=255], nbrOfPits=2}
Grape 1 equals grape 2? false
Grape 1 equals its copy? true

 

아래와 같이 매우 간단하게 작성할 수 있다.

 

public record GrapeRecord(Color color, int nbrOfPits) {
}

private static void basicRecord() {
    record GrapeRecord(Color color, int nbrOfPits) {}
    GrapeRecord grape1 = new GrapeRecord(Color.BLUE, 1);
    GrapeRecord grape2 = new GrapeRecord(Color.WHITE, 2);
    System.out.println("Grape 1 is " + grape1);
    System.out.println("Grape 2 is " + grape2);
    System.out.println("Grape 1 equals grape 2? " + grape1.equals(grape2));
    GrapeRecord grape1Copy = new GrapeRecord(grape1.color(), grape1.nbrOfPits());
    System.out.println("Grape 1 equals its copy? " + grape1.equals(grape1Copy));
}
[Output]

Grape 1 is GrapeClass{color=java.awt.Color[r=0,g=0,b=255], nbrOfPits=1}
Grape 2 is GrapeClass{color=java.awt.Color[r=255,g=255,b=255], nbrOfPits=2}
Grape 1 equals grape 2? false
Grape 1 equals its copy? true

 

동일한 결과를 출력한다.

color가 null인 경우 등의 유효성 검사는 아래와 같이 할 수 있다.

 

private static void basicRecordWithValidation() {
    record GrapeRecord(Color color, int nbrOfPits) {
        GrapeRecord {
            System.out.println("Parameter color=" + color + ", Field color=" + this.color());
            System.out.println("Parameter nbrOfPits=" + nbrOfPits + ", Field nbrOfPits=" + this.nbrOfPits());
            if (color == null) {
                throw new IllegalArgumentException("Color may not be null");
            }
        }
    }
    GrapeRecord grape1 = new GrapeRecord(Color.BLUE, 1);
    System.out.println("Grape 1 is " + grape1);
    GrapeRecord grapeNull = new GrapeRecord(null, 2);
}

 

 

Instanceof 변수 생성

 

instanceof 체크 시에 변수를 생성할 수 있어

이제 새 변수를 생성하고 객체를 캐스팅하는데 추가 줄이 필요하지 않다.

 

private static void oldStyle() {
    Object o = new GrapeClass(Color.BLUE, 2);
    if (o instanceof GrapeClass) {
        GrapeClass grape = (GrapeClass) o;
        System.out.println("This grape has " + grape.getNbrOfPits() + " pits.");
    }
}
private static void patternMatching() {
     Object o = new GrapeClass(Color.BLUE, 2);
     if (o instanceof GrapeClass grape) { // 변수 생성
         System.out.println("This grape has " + grape.getNbrOfPits() + " pits.");
     }
}

 

 

 

컴팩트한 숫자 포맷 지원

 

SHORT 포맷 스타일:

NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT);
System.out.println(fmt.format(1000));
System.out.println(fmt.format(100000));
System.out.println(fmt.format(1000000));


[Output]
1K
100K
1M

 

LONG 포맷 스타일:

fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.LONG);
System.out.println(fmt.format(1000));
System.out.println(fmt.format(100000));
System.out.println(fmt.format(1000000));


[Output]
1 thousand
100 thousand
1 million

 

기간 지원 추가

 

새로운 "B" 패턴이 추가되었다.

 

DateTimeFormatter dtf = DateTimeFormatter.ofPattern("B");
System.out.println(dtf.format(LocalTime.of(8, 0)));
System.out.println(dtf.format(LocalTime.of(13, 0)));
System.out.println(dtf.format(LocalTime.of(20, 0)));
System.out.println(dtf.format(LocalTime.of(23, 0)));
System.out.println(dtf.format(LocalTime.of(0, 0)));


[Output]
in the morning
in the afternoon
in the evening
at night
midnight

 

 

 

Stream.toList()

 

이제 Stream에서 List로 변환할 때 기존의 긴 Collectors.toList() 를 호출하지 않아도 된다.

private static void oldStyle() {
    Stream<String> stringStream = Stream.of("a", "b", "c");
    List<String> stringList =  stringStream.collect(Collectors.toList());
    for(String s : stringList) {
        System.out.println(s);
    }
}

 

자바 17에서는 toList 이전 동작을 대체하는 메서드가 추가 되었다.

private static void streamToList() {
    Stream<String> stringStream = Stream.of("a", "b", "c");
    List<String> stringList =  stringStream.toList();
    for(String s : stringList) {
        System.out.println(s);
    }
}

 

 

끝!

복사했습니다!