JAVA 8(4) - Optinal API

3 분 소요

Optional, Optional API

Java8 이전의 null 처리

NPE(NullPointerException)

null 참조로 인해서 Java로 개발을 할 때 널 포인터 예외 에러를 모두 겪어봣을 것이다. null을 고안해낸 Tony Hoare 조차도 후에 존재하지 않는 값을 표현하는 null 참조를 만든 것을 후회한다고 토로한 적도 있다.

“I call it my billion-dollar mistake

Java8 이전에는 NPE를 막기위해 아래와 같은 방법을 사용했었다.

public Progress getProgress() {
    if (this.progress == null) {
        throw new IllegalStateException();
    }
    return progress;
}

반환하는 측에서 해당 값이 null인 경우 의도적으로 Exception을 발생하는 방법

-> 에러 발생시 스택트레이스를 찍게되고 이는 성능 하락의 원인이 된다,

스택 트레이스 (Stack Trace)

  • 프로그램이 시작된 시점부터 현재 위치까지의 메서드 호출 목록
  • 예외가 어디서 발생했는지 알려주기 위해 JVM이 자동으로 생성함
  • 스택트레이스는 에러가 발생된 시점부터 프로그램이 시작된 시점까지 거슬러 올라가면서 추력하기 때문에 먼저 실행된 메소드가 아래쪽에 위치함.
// 원하는 값: return order.getMember().getAddress().getCity();

public String getCityOfMemberFromOrder(Order order) {
	if (order != null) {
		Member member = order.getMember();
		if (member != null) {
			Address address = member.getAddress();
			if (address != null) {
				String city = address.getCity();
				if (city != null) {
					return city;
				}
			}
		}
	}
	return "Seoul"; // default
}

이전에는 if문으로 유효성을 검사하는 수 밖에 없었다.

이렇게 매번 null 값이 있을 수 있는 곳에 중첩해서 null 값을 체크했던 것이다…

이와 같은 코드는 유지보수도 힘들고. 하나라도 빼먹으면 어디서 null 값이 있는지 다시 코드를 살펴봐야한다..

이와 같은 문제들을 Java8 에서 Optinal 을 통해 해결하게 된 것이다.

Optional

“존재할 수도 있지만 안 할 수도 있는 객체” 즉, null이 될 수도 있는 객체를 감싸고 있는 클래스.

​ 👉 null 체크를 직접 할 필요가 사라짐

간혹 비어있는 객체를 반환하더라도 Optional 객체로 반환값을 감싸준다면 반환되는 값이 null 일지라도 Optional 객체로 래핑되어 반환되기 때문에 안전하다.

주의사항

문법적으로 Optional은 어디에 들어가도 에러가 발생하거나 문제가 되지는 않지만,

공식문서에 따르면 Optional을 리턴값으로만 쓰는 것을 권장한다.

메소드 매개변수 타입, 맵의 키 타입, 인스턴스 필드 타입으로 쓰지 말자

  1. 메소드 매개변수 타입

    public void setProgress(Optional<Progress> progress) {
        if (progress != null) {
            progress.ifPresent(p-> this.progress = p);
        }
    }
    

    매개변수가 null로 들어오는 경우를 고려해야 하기 때문에 결국 progress가 null 인지 검사를 해아하고, progress라는 Optional 타입안에 값이 제대로 들어 있는지 확인하는 ifPresent 메소드도 호출한 후에야 값을 받을 수 있게된다.

  2. 맵의 키 타입

    맵에서 key는 not null 이라는 특징이 있는데, 이 때 key 를 Optional 타입으로 받게되면 맵의 특징에 어긋나는 행동이다.

  3. 인스턴스 필드 타입

    public class OnlineClass {
        private Integer id;
        private String title;
        private boolean closed;
        private Optional<Progress> progress;
    }
    

    필드에 progress를 Optional로 래핑하면 해당 필드가 있을수도 있고 없을 수도 있다는 의미인데 이는 도메인클래스 설계에 관한 문제로 권장되지 않는 방법이다.

리턴 타입이 Optional인 메소드에서 null을 리턴하지 말자

public Optional<Progress> getProgress() {
//     return null;
    return Optional.empty();
}

Collection, Map, Stream Array, Optional은 Optional로 감싸지 말자

Optional로 다른 컨테이너 성격의 인스턴스들을 또 다시 감싸지 말자.

Collection, Map, Stream Array는 그 자체로 빈 값에 대한 처리가 가능한 객체들이기 때문에 굳이 Optional로 감싸게되면 이중래핑이 되기때문에 효율이 떨어진다.

Optional API

1. Optional 만들기

  • Optional.of()

    null이 아닌 객체를 담고 있는 Optional 객체를 생성한다.

    null이 넘어올 경우, NPE를 던지기 때문에 주의해서 사용해야 한다.

  • Optional.ofNullable(value)

    null 인지 아닌지 확신할 수 없는 객체들을 담고 있는 Optional 객체를 생성한다.

    null이 넘어올 경우, NPE를 던지지 않고 Optional.empty()와 동일하게 비어있는 Optional 객체를 얻어온다.

    해당 객체가 null 인지 아닌지 자신이 없는 상황에서는 이 메소드를 사용해야 한다.

  • Optional.empty()

    null을 담고 있는, 한 마디로 비어있는 Optional 객체를 얻어온다.

    주로 반환값이 null일 때 사용한다.

public static void main(String[] args) {
    Optional<Integer> integer = Optional.of(10);
    Optional<Integer> integerOptional = 
						Optional.ofNullable((Math.random() * 10) > 5 ? null : 1);
    Optional<Object> empty = Optional.empty();
}

2. Optional에 값이 있는지 없는지 확인하기

  • isPresent()

    비어있으면 false, 값이 있으면 true를 반환한다.

  • isEmpty() (Java 11 부터 제공)

    비어있으면 true, 값이 있으면 false를 반환한다,

public static void main(String[] args) {
    Optional<Integer> integer = Optional.of(10);
    Optional<Object> empty = Optional.empty();

    System.out.println(integer.isPresent()); // true
    System.out.println(empty.isPresent());   // false
    
    System.out.println(integer.isEmpty());   // false
    System.out.println(empty.isEmpty());     // true
}

Optional에 있는 값 가져오기

  • get()

    비어있는 Optional 객체에 대해서, NoSuchElementException 를 던짐

  • 내부 값이 null인걸 고려하는 경우

    • ifPresent(Consumer)

      Optional에 값이 있는 경우 그 값을 Consumer Functional Interface에 전달 후 로직을 수행

    • orElse(T)

      값이 있는 경우 가져오고, 없는 경우 인자값으로 선언한 내용을 반환

    • orElseGet(Supplier)

      값이 있으면 가져오고, 없는 경우 Supplier Functional interface 로직을 수행

    • orElseThrow()

      값이 있으면 가져오고, 없으면 에러를 던짐

  • 내부 값을 걸러내서 가져오기

    • Optional filter(Predicate)

      Predicate Functional interface을 수행하여 조건에 부합하는 값을 반환

  • 내부 값 반환하기

    • Optional map(Function)

      Function Functional interface을 수행하여 내부 값을 순회하며 변경 후 반환

    • Optional flatMap(Function)

      Optional 안에 들어있는 인스턴스가 Optional인 경우 내부 원소값을 꺼낼 때 사용

    public static void main(String[] args) {
        Optional<Integer> integer = Optional.ofNullable(10);
        Optional<Object> empty = Optional.empty();
      
        System.out.println(integer.get());
        integer.ifPresent(System.out::println);
        integer.orElse(testInteger());
        integer.orElseGet(App::testInteger);
        integer.orElseThrow(NoSuchElementException::new);
      
      
    }
    public static Integer testInteger(){
        System.out.println("create integer test");
        return (int)(Math.random() * 10);
    }
    

Reference

Optional을 Optional 답게

카테고리:

업데이트:

댓글남기기