2021.09.28

'살아 움직이는 언어' 자바에 추가된 6가지 새로운 기능

Matthew Tyson | InfoWorld
자바는 2018년 새로운 릴리즈 주기를 도입하면서 개발 측면에서 가장 큰 변화를 단행했다. 이 과감한 새로운 계획의 결과로 자바 개발자들은 6개월마다 새로운 기능 릴리즈를 받고 있다.
 
자바를 신선하고 현 시점에 맞는 언어로 유지하는 데는 분명 좋지만, 대신 새로운 기능을 놓치기도 쉽다. 새로이 추가된  유용한 기능 6가지를 대략적으로 살펴본다.
 
ⓒ Pixabay License
 

Optional 클래스

널 포인터 예외는 가장 전통적인 오류 가운데 하나다. 익숙한 문제지만 방지하기가 쉽지 않기도 하다. 그러나 자바 8에서 처음 소개되어 자바 10에서 더 개선된 Optional 클래스를 사용하면 더 이상 골칫거리가 아니다.

기본적으로 Optional 클래스는 변수를 래핑한 다음 래퍼의 메서드를 사용해서 널을 더 간편히 다룰 수 있게 해준다.
 
예시 1에는 흔한 널 포인터 오류의 예가 나와 있다. 클래스 레퍼런스인 foo가 널이고, 여기서 메서드인 foo.getName()이 액세스된다.

예시 1. Optional이 없는 널 포인터
public class MyClass {
    public static void main(String args[]) {
      InnerClass foo = null;
      System.out.println("foo = " + foo.getName());
    }
}
class InnerClass {
  String name = "";
  public String getName(){
      return this.name;
  }
}

Optional은 필요에 따라 이 같은 상황에 대처하기 위한 여러 접근 방법을 제공한다. isPresent() 메서드를 사용하면 조건 확인이 가능하다(if-check). 그러나 이 경우 상당히 장황하게 될 수 있다. Optional에는 실용적인 처리를 위한 메서드도 있다. 예시 2는 ifPresent()(isPresent()와는 한 글자 차이)를 사용해서 값이 있는 경우에만 출력 코드를 실행하는 방법을 보여준다.

예시 2. 값이 있는 경우에만 코드 실행
import java.util.Optional;
public class MyClass {
    public static void main(String args[]) {
      InnerClass foo = null; //new InnerClass("Test");
      Optional fooWrapper = Optional.ofNullable(foo);
      fooWrapper.ifPresent(x -> System.out.println("foo = " + x.getName()));
      //System.out.println("foo = " + fooWrapper.orElseThrow());
    }
}
class InnerClass {
  String name = "";
  public InnerClass(String name){
      this.name = name;
  }
  public String getName(){
      return this.name;
  }
}

: Optional을 사용할 때 orElse() 메서드를 사용하여 메서드 호출을 통해 기본값을 제공한다면 그 대신 orElseGet()을 사용해서 함수 참조를 제공하는 방법을 고려할 수 있다. 이렇게 하면 값이 널이 아닐 때 호출을 실행하지 않음으로써 성능상의 이득을 얻을 수 있다.
 

record 클래스(프리뷰 기능)

자바 앱 빌드에서 흔히 필요한 부분은 불변성 DTO(데이터 전송 객체)다. DTO는 데이터베이스, 파일 시스템 및 기타 데이터 저장소에서 데이터를 모델링하는 데 사용된다. 전통적으로 DTO는 getter가 액세스하는 일 없이 멤버가 생성자를 통해 설정되는 클래스를 만드는 방법으로 생성된다. 자바 14에서 처음 도입되어 자바 15에서 개선된 새로운 record 키워드가 바로 이 목적을 위한 간편한 방법을 제공한다.
 
예시 3은 record 형식이 도입되기 전, 기존의 DTO 정의와 사용 방법을 보여준다.

예시 3. 간단한 불변성 DTO
public class MyClass {
    public static void main(String args[]) {
      Pet myPet = new Pet("Sheba", 10);

      System.out.println(String.format("My pet %s is aged %s", myPet.getName(), myPet.getAge()));
    }
}
class Pet {
    String name;
    Integer age;
    public Pet(String name, Integer age){
        this.name = name;
        this.age = age;
    }
    public String getName(){
        return this.name;
    }
    public Integer getAge(){
        return this.age;
    }
}


예시 4와 같이 record 키워드를 사용해서 거추장스러운 요소를 대부분 없앨 수 있다.

예시 4. record 키워드 사용 
public class MyClass {
    public static void main(String args[]) {
      Pet myPet = new Pet("Sheba", 10);

      System.out.println(String.format("My pet %s is aged %s", myPet.getName(), myPet.getAge()));
    }
}

public record Pet(String name, Integer age) {}
   

데이터 객체를 사용하는 클라이언트 코드가 변경되지 않은 것을 볼 수 있다. 일반적으로 정의되는 객체와 똑같이 작동한다. record 키워드는 매우 똑똑해서, 간단한 정의를 통해 어떤 필드가 존재하는지를 추론한다.
 
또한 record 형식은 equals(), hashCode(),toString()의 기본 구현을 정의하며, 개발자가 이러한 기본 구현을 재정의하도록 허용한다. 맞춤형 생성자를 제공할 수도 있다.
 
참고로 record는 서브클래스가 불가능하다.
 

새로운 String 메서드

자바 10과 자바 12에서 유용한 String 메서드가 여러 개 추가됐다. 문자열 조작 메서드 외에 텍스트 파일 액세스를 간소화하기 위한 2개의 새로운 메서드가 도입됐다.
 
자바 10의 새로운 String 메서드 :

•    isBlank() : 문자열이 비어 있거나 문자열에 공백만 포함된 경우(탭 포함) true를 반환한다. 길이가 0인 경우에만 true를 반환하는 isEmpty()와는 다르다.

•    lines() : 문자열을 각각 라인 하나를 포함한 여러 문자열 스트림으로 분할한다. 라인은 /r 또는 /n 또는 /r/n으로 정의된다. 아래 예시 5를 참고하라.

•    strip(), stripLeading(), stripTrailing() : 각각 처음과 끝, 처음, 그리고 끝의 공백을 제거한다.
 
•    repeat(int times) : 원래의 문자열을 받아서 지정된 횟수만큼 반복하는 문자열을 반환한다.

•    readString() : 파일 경로에서 문자열로 직접 읽어들일 수 있도록 한다(예시 6 참조).

•    writeString(Path path) : 지정된 경로의 파일에 직접 문자열을 쓴다.

자바 12의 새로운 String 메서드 :
•    indent(int level): 지정한 만큼 문자열을 들여쓰기한다. 음수 값은 선행 공백에만 영향을 미친다.

•    transform(Function f): 지정된 람다를 문자열에 적용한다.


예시 5. String.lines() 예
import java.io.IOException;
import java.util.stream.Stream;
public class MyClass {
    public static void main(String args[]) throws IOException{
      String str = "test \ntest2 \n\rtest3 \r";
      Stream lines = str.lines();
      lines.forEach(System.out::println);
    }
}

/*
outputs:
test
test2
test3
*/

예시 6. String.readString(Path path) 예
Path path = Path.of("myFile.txt"); 
String text = Files.readString(path);
System.out.println(text);
 

switch 식

자바 12에는 switch를 문 내에서 인라인으로 사용할 수 있게 해주는 switch 식이 도입됐다. 즉, switch 식은 값을 반환한다. 또한 자바 12는 명시적인 break의 필요성을 없애는 화살표 구문도 제공한다. 자바 13은 한 걸음 더 나아가 switch 케이스가 반환하는 값을 명시적으로 나타내는 yield 키워드를 도입했다. 자바 14에서는 새로운 switch 식이 완전한 기능으로 도입됐다.
 
몇 가지 예를 보자. 먼저 예시 7에는 전통적인(자바 8) 형식으로 된 switch 문의 (상당히 부자연스러운) 예가 나와 있다. 이 코드는 변수(message)를 사용해서 숫자의 이름(알려진 경우)을 출력한다.

예시 7. 과거의 자바 switch
class Main { 
  public static void main(String args[]) {
    int size = 3;
    String message = "";

switch (size){
 case 1 :
message = "one";
 case 3 :
   message = "three";
break;
 default :
message = "unknown";
break;
}

System.out.println("The number is " + message);
  }
}

이 코드는 상당히 장황하고 까다롭다. 사실 이미 오류가 있다! 잘 보면 break가 누락되었음을 알 수 있다. 예시 8은 switch 식을 사용해 코드를 간소화한 모습이다.

예시 8. 새로운 switch 식
class NewSwitch { 
  public static void main(String args[]) {
    int size = 3;

    System.out.println("The number is " +
      switch (size) {
        case 1 -> "one";
        case 3 -> "three";
        default -> "unknown";
      }
    );
  }
}

예시 8에서 switch 식은 System.out.println 호출 안에 들어가 있다. 이것만으로 가독성 측면에서 큰 진전이며 불필요한 message 변수도 없어진다. 또한 화살표 구문은 break 문을 제거해 코드의 양을 줄여준다. (yield 키워드는 화살표 구문을 사용하지 않을 때 사용됨)
 
새로운 switch 식 구문에 대한 더 자세한 내용은 여기서 볼 수 있다.
 

텍스트 블록

자바 13은 텍스트 블록을 통해 자바에서 복잡한 텍스트 문자열을 다룰 때의 오랜 불편함을 해소했다. 자바 14는 텍스트 블록 지원을 더 개선했다.
 
JSON, XML, SQL 등의 여러 중첩된 이스케이프 계층을 다루다 보면 정신을 차리기가 어렵다. 사양에는 다음과 같이 설명돼 있다.
 
자바에서 HTML, XML, SQL 또는 JSON 스니펫을 문자열 리터럴 안에 내장하는 경우 이 스니펫이 포함된 코드가 컴파일되려면 일반적으로 먼저 이스케이프 및 연결과 관련해 상당한 편집이 필요하다. 스니펫은 많은 경우 읽기가 어렵고 유지보수도 까다롭다.

예시 9에서 새로운 텍스트 블록 구문을 사용해 JSON 스니펫을 만드는 것을 볼 수 있다.

예시 9. 텍스트 블록을 사용한 JSON
class TextBlock { 
  public static void main(String args[]) {
    String json = """
      {
        "animal" : "Quokka",
        "link" : "https://en.wikipedia.org/wiki/Quokka"
      }
    """;

    System.out.println(json);
  }
}

예시 9에는 이스케이프 문자가 보이지 않는다. 3중 따옴표 구문을 유의해서 보자.
 

sealed 클래스

자바 15(JEP 260)에는 sealed 클래스 개념이 도입됐다. 새로운 sealed 키워드는 인터페이스 서브클래스가 가능한 클래스를 정의할 수 있게 해준다. 한 가지 예를 보는 것이 천 마디 설명보다 낫다. 예시 10을 보자.

예시 10. sealed 클래스 예
public abstract sealed class Pet
    permits Cat, Dog, Quokka {...}


여기서 인터페이스 디자이너는 sealed 키워드를 사용해서 Pet 클래스를 확장하도록 허용된 클래스를 지정한다.
 
전체적으로 자바 릴리즈의 새로운 접근 방식은 제대로 기능하고 있다. JEP(JDK Enhancement Proposal) 프로세스로 들어오는 많은 새로운 아이디어가 실제 사용 가능한 자바 기능으로 구현되고 있다. 자바 개발자에게는 좋은 소식이다. 우리가 다루는 언어와 플랫폼이 활기차게 계속 발전하고 있다는 뜻이기 때문이다. editor@itworld.co.kr 
 


2021.09.28

'살아 움직이는 언어' 자바에 추가된 6가지 새로운 기능

Matthew Tyson | InfoWorld
자바는 2018년 새로운 릴리즈 주기를 도입하면서 개발 측면에서 가장 큰 변화를 단행했다. 이 과감한 새로운 계획의 결과로 자바 개발자들은 6개월마다 새로운 기능 릴리즈를 받고 있다.
 
자바를 신선하고 현 시점에 맞는 언어로 유지하는 데는 분명 좋지만, 대신 새로운 기능을 놓치기도 쉽다. 새로이 추가된  유용한 기능 6가지를 대략적으로 살펴본다.
 
ⓒ Pixabay License
 

Optional 클래스

널 포인터 예외는 가장 전통적인 오류 가운데 하나다. 익숙한 문제지만 방지하기가 쉽지 않기도 하다. 그러나 자바 8에서 처음 소개되어 자바 10에서 더 개선된 Optional 클래스를 사용하면 더 이상 골칫거리가 아니다.

기본적으로 Optional 클래스는 변수를 래핑한 다음 래퍼의 메서드를 사용해서 널을 더 간편히 다룰 수 있게 해준다.
 
예시 1에는 흔한 널 포인터 오류의 예가 나와 있다. 클래스 레퍼런스인 foo가 널이고, 여기서 메서드인 foo.getName()이 액세스된다.

예시 1. Optional이 없는 널 포인터
public class MyClass {
    public static void main(String args[]) {
      InnerClass foo = null;
      System.out.println("foo = " + foo.getName());
    }
}
class InnerClass {
  String name = "";
  public String getName(){
      return this.name;
  }
}

Optional은 필요에 따라 이 같은 상황에 대처하기 위한 여러 접근 방법을 제공한다. isPresent() 메서드를 사용하면 조건 확인이 가능하다(if-check). 그러나 이 경우 상당히 장황하게 될 수 있다. Optional에는 실용적인 처리를 위한 메서드도 있다. 예시 2는 ifPresent()(isPresent()와는 한 글자 차이)를 사용해서 값이 있는 경우에만 출력 코드를 실행하는 방법을 보여준다.

예시 2. 값이 있는 경우에만 코드 실행
import java.util.Optional;
public class MyClass {
    public static void main(String args[]) {
      InnerClass foo = null; //new InnerClass("Test");
      Optional fooWrapper = Optional.ofNullable(foo);
      fooWrapper.ifPresent(x -> System.out.println("foo = " + x.getName()));
      //System.out.println("foo = " + fooWrapper.orElseThrow());
    }
}
class InnerClass {
  String name = "";
  public InnerClass(String name){
      this.name = name;
  }
  public String getName(){
      return this.name;
  }
}

: Optional을 사용할 때 orElse() 메서드를 사용하여 메서드 호출을 통해 기본값을 제공한다면 그 대신 orElseGet()을 사용해서 함수 참조를 제공하는 방법을 고려할 수 있다. 이렇게 하면 값이 널이 아닐 때 호출을 실행하지 않음으로써 성능상의 이득을 얻을 수 있다.
 

record 클래스(프리뷰 기능)

자바 앱 빌드에서 흔히 필요한 부분은 불변성 DTO(데이터 전송 객체)다. DTO는 데이터베이스, 파일 시스템 및 기타 데이터 저장소에서 데이터를 모델링하는 데 사용된다. 전통적으로 DTO는 getter가 액세스하는 일 없이 멤버가 생성자를 통해 설정되는 클래스를 만드는 방법으로 생성된다. 자바 14에서 처음 도입되어 자바 15에서 개선된 새로운 record 키워드가 바로 이 목적을 위한 간편한 방법을 제공한다.
 
예시 3은 record 형식이 도입되기 전, 기존의 DTO 정의와 사용 방법을 보여준다.

예시 3. 간단한 불변성 DTO
public class MyClass {
    public static void main(String args[]) {
      Pet myPet = new Pet("Sheba", 10);

      System.out.println(String.format("My pet %s is aged %s", myPet.getName(), myPet.getAge()));
    }
}
class Pet {
    String name;
    Integer age;
    public Pet(String name, Integer age){
        this.name = name;
        this.age = age;
    }
    public String getName(){
        return this.name;
    }
    public Integer getAge(){
        return this.age;
    }
}


예시 4와 같이 record 키워드를 사용해서 거추장스러운 요소를 대부분 없앨 수 있다.

예시 4. record 키워드 사용 
public class MyClass {
    public static void main(String args[]) {
      Pet myPet = new Pet("Sheba", 10);

      System.out.println(String.format("My pet %s is aged %s", myPet.getName(), myPet.getAge()));
    }
}

public record Pet(String name, Integer age) {}
   

데이터 객체를 사용하는 클라이언트 코드가 변경되지 않은 것을 볼 수 있다. 일반적으로 정의되는 객체와 똑같이 작동한다. record 키워드는 매우 똑똑해서, 간단한 정의를 통해 어떤 필드가 존재하는지를 추론한다.
 
또한 record 형식은 equals(), hashCode(),toString()의 기본 구현을 정의하며, 개발자가 이러한 기본 구현을 재정의하도록 허용한다. 맞춤형 생성자를 제공할 수도 있다.
 
참고로 record는 서브클래스가 불가능하다.
 

새로운 String 메서드

자바 10과 자바 12에서 유용한 String 메서드가 여러 개 추가됐다. 문자열 조작 메서드 외에 텍스트 파일 액세스를 간소화하기 위한 2개의 새로운 메서드가 도입됐다.
 
자바 10의 새로운 String 메서드 :

•    isBlank() : 문자열이 비어 있거나 문자열에 공백만 포함된 경우(탭 포함) true를 반환한다. 길이가 0인 경우에만 true를 반환하는 isEmpty()와는 다르다.

•    lines() : 문자열을 각각 라인 하나를 포함한 여러 문자열 스트림으로 분할한다. 라인은 /r 또는 /n 또는 /r/n으로 정의된다. 아래 예시 5를 참고하라.

•    strip(), stripLeading(), stripTrailing() : 각각 처음과 끝, 처음, 그리고 끝의 공백을 제거한다.
 
•    repeat(int times) : 원래의 문자열을 받아서 지정된 횟수만큼 반복하는 문자열을 반환한다.

•    readString() : 파일 경로에서 문자열로 직접 읽어들일 수 있도록 한다(예시 6 참조).

•    writeString(Path path) : 지정된 경로의 파일에 직접 문자열을 쓴다.

자바 12의 새로운 String 메서드 :
•    indent(int level): 지정한 만큼 문자열을 들여쓰기한다. 음수 값은 선행 공백에만 영향을 미친다.

•    transform(Function f): 지정된 람다를 문자열에 적용한다.


예시 5. String.lines() 예
import java.io.IOException;
import java.util.stream.Stream;
public class MyClass {
    public static void main(String args[]) throws IOException{
      String str = "test \ntest2 \n\rtest3 \r";
      Stream lines = str.lines();
      lines.forEach(System.out::println);
    }
}

/*
outputs:
test
test2
test3
*/

예시 6. String.readString(Path path) 예
Path path = Path.of("myFile.txt"); 
String text = Files.readString(path);
System.out.println(text);
 

switch 식

자바 12에는 switch를 문 내에서 인라인으로 사용할 수 있게 해주는 switch 식이 도입됐다. 즉, switch 식은 값을 반환한다. 또한 자바 12는 명시적인 break의 필요성을 없애는 화살표 구문도 제공한다. 자바 13은 한 걸음 더 나아가 switch 케이스가 반환하는 값을 명시적으로 나타내는 yield 키워드를 도입했다. 자바 14에서는 새로운 switch 식이 완전한 기능으로 도입됐다.
 
몇 가지 예를 보자. 먼저 예시 7에는 전통적인(자바 8) 형식으로 된 switch 문의 (상당히 부자연스러운) 예가 나와 있다. 이 코드는 변수(message)를 사용해서 숫자의 이름(알려진 경우)을 출력한다.

예시 7. 과거의 자바 switch
class Main { 
  public static void main(String args[]) {
    int size = 3;
    String message = "";

switch (size){
 case 1 :
message = "one";
 case 3 :
   message = "three";
break;
 default :
message = "unknown";
break;
}

System.out.println("The number is " + message);
  }
}

이 코드는 상당히 장황하고 까다롭다. 사실 이미 오류가 있다! 잘 보면 break가 누락되었음을 알 수 있다. 예시 8은 switch 식을 사용해 코드를 간소화한 모습이다.

예시 8. 새로운 switch 식
class NewSwitch { 
  public static void main(String args[]) {
    int size = 3;

    System.out.println("The number is " +
      switch (size) {
        case 1 -> "one";
        case 3 -> "three";
        default -> "unknown";
      }
    );
  }
}

예시 8에서 switch 식은 System.out.println 호출 안에 들어가 있다. 이것만으로 가독성 측면에서 큰 진전이며 불필요한 message 변수도 없어진다. 또한 화살표 구문은 break 문을 제거해 코드의 양을 줄여준다. (yield 키워드는 화살표 구문을 사용하지 않을 때 사용됨)
 
새로운 switch 식 구문에 대한 더 자세한 내용은 여기서 볼 수 있다.
 

텍스트 블록

자바 13은 텍스트 블록을 통해 자바에서 복잡한 텍스트 문자열을 다룰 때의 오랜 불편함을 해소했다. 자바 14는 텍스트 블록 지원을 더 개선했다.
 
JSON, XML, SQL 등의 여러 중첩된 이스케이프 계층을 다루다 보면 정신을 차리기가 어렵다. 사양에는 다음과 같이 설명돼 있다.
 
자바에서 HTML, XML, SQL 또는 JSON 스니펫을 문자열 리터럴 안에 내장하는 경우 이 스니펫이 포함된 코드가 컴파일되려면 일반적으로 먼저 이스케이프 및 연결과 관련해 상당한 편집이 필요하다. 스니펫은 많은 경우 읽기가 어렵고 유지보수도 까다롭다.

예시 9에서 새로운 텍스트 블록 구문을 사용해 JSON 스니펫을 만드는 것을 볼 수 있다.

예시 9. 텍스트 블록을 사용한 JSON
class TextBlock { 
  public static void main(String args[]) {
    String json = """
      {
        "animal" : "Quokka",
        "link" : "https://en.wikipedia.org/wiki/Quokka"
      }
    """;

    System.out.println(json);
  }
}

예시 9에는 이스케이프 문자가 보이지 않는다. 3중 따옴표 구문을 유의해서 보자.
 

sealed 클래스

자바 15(JEP 260)에는 sealed 클래스 개념이 도입됐다. 새로운 sealed 키워드는 인터페이스 서브클래스가 가능한 클래스를 정의할 수 있게 해준다. 한 가지 예를 보는 것이 천 마디 설명보다 낫다. 예시 10을 보자.

예시 10. sealed 클래스 예
public abstract sealed class Pet
    permits Cat, Dog, Quokka {...}


여기서 인터페이스 디자이너는 sealed 키워드를 사용해서 Pet 클래스를 확장하도록 허용된 클래스를 지정한다.
 
전체적으로 자바 릴리즈의 새로운 접근 방식은 제대로 기능하고 있다. JEP(JDK Enhancement Proposal) 프로세스로 들어오는 많은 새로운 아이디어가 실제 사용 가능한 자바 기능으로 구현되고 있다. 자바 개발자에게는 좋은 소식이다. 우리가 다루는 언어와 플랫폼이 활기차게 계속 발전하고 있다는 뜻이기 때문이다. editor@itworld.co.kr 
 


X