Optional\
Optional<T>
은 메소드의 결과가 명백하게 결과 없음
을 반환할 이유가 있고, null
을 반환하게 될 시 문제가 될 수 있는 경우에 사용할 수 있다.
API NOTE:
Optional is primarily intended for use as a method return type where there is a clear need to represent “no result,” and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.
Optional 선언 값이 없는 optional 1 Optional<Foo> foo = Optional.empty();
Optional 클래스에서는 static final
으로 빈 Optional을 정의하고 이를 리턴하고 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public final class Optional <T> { private static final Optional<?> EMPTY = new Optional <>() ... public static <T> Optional<T> empty () { @SuppressWarnings("unchecked") Optional<T> t = (Optional<T>) EMPTY; return t; } }
값이 있는 optional 1 Optional<Foo> foo = Optional.of(new Foo ());
새로운 Optional 클래스를 생성해서 내려준다.
1 2 3 public static <T> Optional<T> of (T value) { return new Optional <>(value); }
값의 유무가 불투명한 optional 1 2 Optional<Foo> foo = Optional.ofNullable(new Foo ()); Optional<Foo> nullFoo = Optional.ofNullable(null );
내부에서 null
여부를 확인하고 null이라면 empty()
를, 아니라면 of(value)
를 호출해 결과를 내려준다.
1 2 3 public static <T> Optional<T> ofNullable (T value) { return value == null ? empty() : of(value); }
중간 연산 filter filter()
의 조건을 통해 필터링을 할 수 있다. filter()
는 매개변수로 Predicate
함수형 인터페이스를 받으며 Predicate
는 파라미터는 1개 리턴타입은 boolean인 인터페이스이다.
1 2 3 4 Optional<Foo> foo = Optional.ofNullable(new Foo (3 )); foo.filter(f -> f.getIntValue() > 3 );
map map()
을 통해 mapping이 가능하다. map()
은 매개변수로 Function<T,R>
함수형 인터페이스를 받으며 T는 파라미터 타입 R은 리턴 타입이다.
1 2 Optional<Foo> foo = Optional.ofNullable(new Foo (3 )); foo.map(Foo::getIntValue);
flatMap flatMap()
을 통해서도 mapping이 가능하다. flatMap()
은 매개변수로 Function<T,R>
함수형 인터페이스를 받으며 T는 파라미터 타입 R은 리턴 타입이다. flatMap()
은 map()
과 유사하지만, 반환하려하는 객체가 Optional<T>
로 감싸져 있을 때 사용한다.
1 2 Optional<Foo> foo = Optional.ofNullable(new Foo (Optional.of(new Bar ()))); foo.flatMap(Foo::getOptionalBar);
종단 연산 get 내부의 값을 반환 받고 싶을 때 사용한다. 만약 value가 null
이라면 NoSuchElementException
을 던진다.
1 2 Optional<Foo> foo = Optional.ofNullable(new Foo (3 )); foo.get();
orElse orElse(other)
는 value가 null
일 경우 other를 아닐경우 value를 내려준다.
1 2 3 4 5 Optional<Foo> nullFoo = Optional.empty(); Optional<Foo> foo = Optional.of(new Foo (3 )); nullFoo.orElse(new Foo (2 )); foo.orElse(new Foo (2 ));
orElseGet orElseGet()
은 null
일 경우 Supplier
를 통해 결과값을 내려받는다. Supplier
는 파라미터는 없고 리턴값이 있는 함수형 인터페이스다.
1 2 3 4 5 6 7 8 9 10 11 Optional<Foo> nullFoo = Optional.empty(); Optional<Foo> foo = Optional.of(new Foo (3 )); nullFoo.orElseGet(() -> { System.out.println("Generating new Foo" ); return new Foo (2 ); }); foo.orElseGet(() -> { System.out.println("Generating new Foo" ); return new Foo (2 ); });
orElseThrow orElseThrow()
는 null
일 경우 Supplier
를 통해 예외를 던진다. Throwable
을 상속한 예외를 리턴값으로 내려주기 때문에 예외를 던져야만 한다.
1 2 3 4 5 6 7 8 9 10 11 Optional<Foo> nullFoo = Optional.empty(); Optional<Foo> foo = Optional.of(new Foo (3 )); nullFoo.orElseThrow(() -> { System.out.println("NPE" ); return new NullPointerException (); }); foo.orElseThrow(() -> { System.out.println("NPE" ); return new NullPointerException (); });
ifPresent ifPresent()
는 null
이 아닐 경우 Consumer<T>
를 통해 어떤 작업을 수행한다.
1 2 3 4 5 6 7 8 9 Optional<Foo> nullFoo = Optional.empty(); Optional<Foo> foo = Optional.of(new Foo (3 )); nullFoo.ifPresent(f -> { System.out.println(f.getIntValue()); }); foo.ifPresent(f -> { System.out.println(f.getIntValue()); });
Anti Patterns 1. null을 Optional에 할당하지 않기 optional
은 단순히 컨테이너일 뿐이다. null
을 표현하고 싶다면 .empty()
를 사용하자.
1 2 3 - Optional<Foo> nullFoo = null; + Optional<Foo> nullFoo = Optional.empty();
2. Optional.get()을 사용하기 전에 값이 존재하는지 확인하기 NPE
를 마주하고 싶지 않다면 피하자.
1 2 3 4 5 6 7 8 9 10 Optional<Foo> foo = ???? //비어 있을 수도 있음 - foo.get(); + if(foo.isPresent()) { + Foo myFoo = foo.get(); + // myFoo를 사용한 작업 + } else { + // myFoo가 필요 없는 작업 + }
3. 값이 없을때 고정 값을 내려주는 행동은 Optional.orElseGet()으로 하기 값이 존재 하지 않을 때는 orElse()
보다도 orElseGet()
이 더 좋다. Supplier
는 lazy evaluation
이기 때문이다.
1 2 3 4 5 6 7 8 9 Optional<Foo> foo = ???? - if(foo.isPresent()) { - return foo.get(); - } else { - return MAX_VALUE_FOO; - } + return foo.orElseGet(this::MAX_VALUE_FOO);
4. 값이 없을때 예외를 던지고 싶다면 Optional.orElseThrow()로 하기 Java10
에서는 Supplier
가 없는 Optional.orElseThrow()
도 존재하지만 명확한 예외를 던져주는게 좋다.
1 2 3 4 5 6 7 8 9 Optional<Foo> foo = ???? - if(foo.isPresent()) { - return foo.get(); - } else { - throw new IllegalStateException(); - } + return foo.orElseThrow(IllegalAccessError::new);
5. 값이 없을때 작업을 수행하지 않는다면 ifPresnet() 쓰기 이와 같은 맥락으로 Java9
부터는 ifPresentOrElse()
가 있다.
1 2 3 4 5 6 7 Optional<String> str = Optional.of("hello world"); - if(foo.isPresent()) { - System.out.println(str.get()); - } + str.ifPresent(System.out::println);
6. 값이 없을 때 Optional를 리턴하고 싶으면 or() 쓰기 Java9
부터 해당이 된다.
1 2 3 4 5 6 7 8 9 10 11 Optional<String> str = ??? - if(str.isPresent()) { - return str; - } else { - return Optional.of("VALUE WAS EMPTY"); - } // 이 방법이 최선 - return str.orElseGet(() -> Optional.of("VALUE WAS EMPTY")); // 존재하지도 않는 방법 + return str.or(() -> Optional.of("VALUE WAS EMPTY"));
7. Optional을 필드에 정의하지 않기 Optional
은 Serializable
을 구현하지 않았기 때문에 해서는 안된다. 단, Jackson
은 Optional
을 직렬화 하는 기능을 구현하고 있음.
1 2 3 4 5 6 7 8 9 public class Foo { - [access_modifier] [static] [final] Optional<String> zip; - [access_modifier] [static] [final] Optional<String> zip = Optional.empty(); - ... + [access_modifier] [static] [final] String zip; + [access_modifier] [static] [final] String zip = ""; + ... }
8. 생성자 아규먼트로 Optional 사용하지 않기 Optional
을 생성자 아규먼트로 사용하는 행동은 또 다른 보일러 플레이트 코드를 양산하게 된다. 같은 맥락으로 Setter
나 여타 다른 메소드의 파라미터에서도 마찬가지다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Foo { private String lastName; // null이면 안됨 - private Optional<String> firstName; // null 일수도 있음 + private String firstName; - public Foo(String lastName, Optional<String> firstName) { + public Foo(String lastName, String firstName) { this.lastName = Objects.requireNonNull(lastName, () -> "Last Name can not be null"); this.firstName = firstName; } public Optional<String> getFirstName() { - return firstName; + return Optional.ofNullable(firstName); } }
9. Optional로 빈 컬렉션이나 배열을 반환하지 않기 1 2 3 4 List<String> names = attendence.getNames(); - return Optional.ofNullable(names); + return names == null ? Collections.emptyList() : names;
10. int, long, double에는 Non-Generic Optional 쓰기 OptionalInt
OptionalLong
OptionalDouble
은 값과 isPresent
라는 boolean
필드를 포함하는 객체이다.
1 2 - Optional<Integer> myInt = Optional.of(1); + OptionalInt myInt = OptionalInt.of(1);
11. 동등성 검사는 랩핑을 벗길 필요가 없다 다음은 Optional.equals()
의 구현이다. 내부적으로 랩핑을 벗겨서 검사해준다.
1 2 3 4 5 6 7 8 9 10 11 12 13 @Override public boolean equals (Object obj) { if (this == obj) { return true ; } if (!(obj instanceof Optional)) { return false ; } Optional<?> other = (Optional<?>) obj; return Objects.equals(value, other.value); }
1 2 3 4 5 Optional<Foo> foo1 = Optional.of(new Foo("fooString")); Optional<Foo> foo2 = Optional.of(new Foo("fooString")); - assertEquals(foo1.get(), foo2.get()); + assertEquals(foo1, foo2);
12. Optional을 Stream에서 사용할때는 Optional.stream()을 쓰자 Java9
이상부터 지원한다.
1 2 3 4 List<Foo> fooList = ... - fooList.stream().map(Foo::getFooString).filter(Optional::isPresent).map(Optional::get).collect(Collectors.toList()); + fooList.stream().map(Foo::getFooString).flatMap(Optional::stream).collect(Collectors.toList());
참조