Java Technical

Java 14 New Feature

Java 버전이 어디서 부터인지 모르게 Major 버전업이 매우 빠르게 진행하는 추세다. 거의 6개월 단위로 이뤄지고 있는거 같은데, 문법이라더니 내부 동작방식이 바뀌는 내용이라 Major 버전이 맞는거 같긴 하지만, 그래도 너무 빠른 추세에 현재 쓰고 있는 버전이 너무 오래된 것 같은 기분이 상대적으로 들게 된다.

Java 14에서는 또 어떻게 달라졌는지 한번 빠르게 정리 해 보는 내용으로 간략하게 시작해 보자.

Java version history

위키피디아에 보면, 지금까지의 출시 일자와 앞으로의 계획이 이미 준비되어 있다. Java 9 이후에는 6개월 이후 마다 출시되고 있다. 한자리 숫자의 버전을 쓰고 있는 경우가 대부분일 텐데, 벌써 14라는 버전까지 출시 되었는데다가 올해 말해는 15버전까지 이미 계획이 되어 있다니, 놀랄수 밖에 없다.

https://en.wikipedia.org/wiki/Java_version_history

New Feature

실제 현업에서 14버전을 사용하기에는 여러가지 어려움이 있을 수 있겠지만, 그래도 어떤 부분에서 변화가 있는지 하나씩 훌어보자.

JEP 305 — Pattern Matching for instanceof

instanceof 연산자를 사용함에 있어서 패턴 일치할 때 유용함을 제공한다는 내용이다. 조건부에 대한 추출이 간결해 지고 보다더 안전하게 사용할 수 있게 되었다. 참고로, Java는 JSR이라는 스펙을 정의한다. (일전의 날짜 관련해서도 언급한 이력이 있다.) 그런데 JSR 스펙 정의시에 JVM에 필요한 기능 제의를 요청해야 하는데, 이를 JEP라고 한다. (JEP Index – http://openjdk.java.net/jeps/0)

if (obj instanceof String s) {
    // can use s here
} else {
    // can't use s here
}

사용할수 있고 없는 블럭이 명확해 진다. 아래 내용을 보면, instanceof를 사용하는 패턴에서 항상 발생하던 Casting도 자연스럽게 해소된다.

@Override public boolean equals(Object o) { 
    return (o instanceof CaseInsensitiveString) && 
        ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); 
}

Using a type test pattern means it can be rewritten to the clearer:

@Override public boolean equals(Object o) { 
    return (o instanceof CaseInsensitiveString cis) && 
        cis.s.equalsIgnoreCase(s); 
}

JEP 358 — Helpful NullPointerExceptions

NullPointerException에 대한 메시지가 매우 친절해졌다. 중급 이상이 되면, 오류를 보자마자 어디구나 감이 오지만, 초급에게는 이유를 찾기 어려운 내용일 때도 많다. 이런 사항에 대해 드디어, 친근한 메시지로 NullPointerException 이 개선되어졌다. 다른 개발 언어에서 접하게된 이야기 이지만, Null Reference를 처음 만든 토니 호어가 ‘Billion dollar mistake(10억 불짜리 실수)’라고 회고되어 더욱 유명하게 알려진데다가, 개발자 중에 매 순간마다 만나보지 않은이가 없게 된 NullPointerException이 어떻게 친절해 졌는지 확인해 보자.

a.i = 99;
a.b.c.i = 99;
a[i][j][k] = 99;
a.i = b.j;

4가지 코드가 실전에 그렇게 쓰이겠냐는 개발자 마다 패턴이 다양하고 가이드가 다르니 이야기 하기 어렵겠지만, 코드만 두고 봤을 때 NullPointerException이 발생되었다면, 어떤 부분으로 발생 되었는지 바로 찍을 수 있을까? 생각보다 쉽지 않다고 생각한다. 따라서, 오류메시지는 다음과 같이 상세하게 나타내어 준다.

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

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

Exception in thread "main" java.lang.NullPointerException:
        Cannot load from object array because "a[i][j]" is null
    at Prog.main(Prog.java:5)

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

JEP 359 — Records

Records는 불변의 순수 데이터를 보관하기 위한 새로운 유형(class, interface 등과 같은)의 등장이다. 일단 Java로 개발하다가 답답함을 느낄때는 In/Out 객체를 만드는 과정에 소모되는 반복적인 선언과 그에 부속되는 항목을 작성하는 부분(Java로 Json 데이터 포멧을 처리를 하다보면, 시대가 참 많이 변화되었다라는…)에 장황 하다고 할 수 밖에 없는 내용들이 있는데, 이런것들에서 전용 유형이 새롭게 등장 하게되어, 상당 부분 패턴 변화가 나타나게 되는 부분이다. 물론 lombok과 여타의 3rd party library로 그 불편함을 개선해서 쓰고 있지만, 불편한건 사실이다. 일단, 말이 길다 코드를 보자.

public class SampleRecord {
   private final String name;
   private final Integer age;
   private final Address address;
 
   public RangeRecord(int lo, int hi) {
      this.lo = lo;
      this.hi = hi;
   }
 
   public int getLo() {
      return lo;
   }
 
   public int getHi() {
      return hi;
   }
}

Lombok을 사용해서 더 간단하게 하겠지만, 일반적인 경우에는 위와 같을 수 밖에다. 그러나 Record가 도입되면, 간단하게 생성할 수 있다.

public record RangeRecord(
   int lo,
   int hi
) {}

추가로, 특정한 조건 시 생성에 제한을 두고 싶다면 아래처럼 할 수 도 있다.

public record RangeRecord(
   int lo,
   int hi
) {
  public RangeRecord {
    if (lo > hi)  /* referring here to the implicit constructor parameters */
      throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi));
  }
}

JEP 361 — Switch Expressions

Switch 문장의 변화이다. Go 언어의 인기에 영향을 받은 것인지 지금까지는 약간 과장되서 표현하자면, If만 쓰자면 단조롭고, 가독성을 위해 쓰는 Switch 정도였다면, 획기적으로 간결해 졌다. 일단, 지금까지 쓰던 Switch 패턴의 코드를 보자.

switch (day) {
    case MONDAY:
    case FRIDAY:
    case SUNDAY:
        System.out.println(6);
        break;
    case TUESDAY:
        System.out.println(7);
        break;
    case THURSDAY:
    case SATURDAY:
        System.out.println(8);
        break;
    case WEDNESDAY:
        System.out.println(9);
        break;
}

그러나, Java 14에서는

switch (day) {
    case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
    case TUESDAY                -> System.out.println(7);
    case THURSDAY, SATURDAY     -> System.out.println(8);
    case WEDNESDAY              -> System.out.println(9);
}

Java 13에서 이미 알려진, yield 키워드에 대한 Yeilding(JEP 354 : break 없는 throw 방식)도 가능하다. 이미 Java 13에서 거의 대부분 알려진 내용과 동일하다.

int j = switch (day) {
    case MONDAY  -> 0;
    case TUESDAY -> 1;
    default      -> {
        int k = day.toString().length();
        int result = f(k);
        yield result;
    }
};

JEP 368 — Text Blocks

현업에서 SQL 문장이 말들어 지는 String 객체를 보면, 정적검사에 매번 걸리는 항목인데, 아무 속시원하게 제공되게 되었다. (이런걸 보면, 과연 개발자가 잘못인가 Java가 잘못인가 아님 고친놈이 잘못을 인정한 것인가 라는 의미없는 논리 싸움에 승리를 안겨주게 된다.)

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

새롭게 등장된 텍스트 블록으로 이스케이프 시퀀스 없이 원하는 만큼의 리터럴 문자열을 기록 할 수 있다.

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

마무리

사실 현장에서 일하다 보면, 이와 같은 변화의 체감을 얻기는 쉽지 않다. 언어 변화보다는 업무변화에 대한 민감도가 체감이 훨씬 높기 때문이다. 현장의 요구사항과 발전은 언어의 발전 기반에서 수행되는 최상단 변화임으로 더욱 그렇겠지만, 그만큼 언어가 시대적 변화에 잘 따라준다면 그만큼 일의 변화에도 유연하게 가능해 진다는 의미 일 수도 있다.

최근에는 유행중인 Go언어를 학습하고 있는데, 상대적으로 이런 내용을 이미 많이 내포 하고 있다고 인식되기도 하지만, 더 형님 뻘 되는 Java 도 시간에 따라 적응하기 위해 언어 자체에서 지속적으로 많은 부분 변화에 따른 것도 사실이다.

변화되는 개발언어에 가볍게 머리속에 담아 두었다가 혹시나 기회가 되어 도전적으로 사용할 기회가 된다면, 그 때에는 한가닥 뇌리의 추억이 도움이 되어 사용되었으면 하는 마음으로 정리해 본다.

댓글 남기기

이 사이트는 스팸을 줄이는 아키스밋을 사용합니다. 댓글이 어떻게 처리되는지 알아보십시오.

%d 블로거가 이것을 좋아합니다: