[JAVA] Java 8 (2)

Seongje kim, 16 October 2020

이번 포스트는 Java 8의 새로운 기능에 대한 2부 내용입니다.

Null 처리


만일 어떤 API 또는 메서드가 경우에 따라 null을 반환한다면 이들을 호출하는 클라이언트 입장에서는 NullPointerException과 같은 이슈를 피하기 위해 null 처리에 대한 방어 코드를 추가하여야 한다. 결과적으로 null을 반환하는 API 또는 메서드는 사용하기 어려울 뿐만 아니라 오류 처리 코드가 늘어지만 그렇다고 성능이 향상되는 것이 아니다.

Java 8 이전에는 어떤 메서드가 특정 조건에서 값을 반환할 수 없을 때, 2가지 방법으로 처리하였다.

  1. 예외 처리 : 예외는 진짜 예외적인 상황에서 사용해야 하며, 예외 생성 비용도 무시할 수 없음
  2. null 반환 : 클라이언트에게 별도의 null 처리 코드를 강제함

하지만 Java 8부터 등장한 Optional<T> 클래스를 사용하면 예외를 던지는 것보다 유연하며 사용하기 쉽고, null을 반환하는 것보다 오류 가능성이 적다는 이점을 가질 수 있다.

Optional<T>

Optional<T>null이 아닌 T 타입의 참조를 담거나 아무것도 담지 않을 수 있다. 어떤 메서드가 보통의 경우 T 타입의 참조를 반환해야 하지만 특정 조건에서 아무것도 반환하지 않아야 한다면 T 대신 Optional<T>를 반환하도록 하면 된다.

public <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
    if (c.isEmpty()) return Optional.empty();

    E result = null;

    for(E e : c) {
        if (result == null || e.compareTo(result) > 0) {
            result = Objects.requireNonNull(e);
        }
    }

    return Optional.of(result);
}

위 예제 메서드는 파라미터로 받은 컬렉션의 최대 값을 구하는 메서드이다. 만일 이 메서드가 최대 값을 찾지 못하였을 경우에 Optional을 반환하는 것을 확인할 수 있다.

Optional은 반환 값이 없을 수도 있음을 클라이언트에게 명확히 알려주는 역할을 한다. 그러므로 예외 처리와 비슷하게 Optional을 반환하는 메서드를 호출하는 클라이언트는 값을 받지 못할 경우의 행동을 선택해야 한다.

ArrayList<String> words = new ArrayList<>();

String result1 = max(words).orElse("단어 없음");            // 기본 값 설정
String result2 = max(words).orElseThrow(Exception::new);   // 원하는 예외를 던짐
String result3 = max(words).get();                         // 항상 값이 채워져 있다고 가정

위 방법들을 포함하여 Optional<T> 클래스가 제공하는 여러 메서드들을 통해 다양하게 처리할 수 있다.

하지만 배열, 컬렉션, 스트림과 같은 컨테이너 타입은 Optional로 감싸기 보단 비어있는 컨테이너를 반환하는 것이 바람직하다. 빈 컨테이너를 생성하는 것이 성능 저하에 영향을 주지 않을 뿐만 아니라 불변 객체를 반환함으로써 클라이언트 해당 객체를 재사용할 수 있기 때문이다. 또한 이는 null 처리에 대한 추가적인 방어 코드 및 Optional 생성으로 발생하는 비용면에서 자유로워질 수 있다.

결과적으로 Optional을 반환하는 경우 클라이언트가 반환 값이 없을 수도 있음을 명확하게 알 수 있고, 이를 처리하는 것에 대한 유연함과 오류 가능성을 줄일 수 있는 장점을 갖지만 Optional 생성에 따른 비용이 추가되기 때문에 성능과 함께 Optional 사용을 고려해야 한다. 만일 어떤 메서드가 성능에 민감하다면 클라이언트에게 부담을 주더라도 null 이나 예외를 던지는 방식이 더 나을 수 있다.

Time API


이전의 Java 버전에서는 날짜 및 시간에 관한 처리를 담당하는 Date, Calendar 클래스를 제공하였다. 하지만 Calendar 클래스는 아래와 같은 문제점을 가지고 있다.

  1. Calendar 인스턴스는 불변 객체가 아니기 때문에 값이 수정될 위험이 있다.
  2. 윤초와 같은 특별한 상황을 고려하지 않는다.
  3. 월(month)을 표현할 때, 0 ~ 11로 표현해야 하는 불편함이 있다.

Java 8에 새롭게 추가된 java.time 패키지는 위와 같은 문제점을 모두 해결했으며, 다양한 기능을 지원하는 다수의 하위 패키지를 포함한다. 대표적으로 LocalDate, LocalTime, LocalDateTime 등의 클래스들이 있다.

다양한 기능을 지원하는 하위 패키지

  1. ISO-8601에 정의된 표준 달력 이외의 달력 시스템을 사용할 때 필요한 클래스 : java.time.chrono
  2. 날짜와 시간에 대한 데이터를 구문 분석하고 형식화하는 데 사용되는 클래스 : java.time.format
  3. 날짜와 시간에 대한 데이터를 연산하는 데 사용되는 보조 클래스 : java.time.temporal
  4. 타임 존(Time-zone)과 관련된 클래스 : java.time.zone
  5. 날짜 및 시간의 차이를 표현하는 클래스 : java.time.preiod, java.time.duration