예외 던지기
null 반환 (객체 참조 케이스의 경우)
2가지가 존재했다.
불변
컬렉션이다.Optional<T>
사용Optional.empty()
Optional.of(value)
Optional.ofNullable(value)
예외로 던지는 방식
public static <E extends Comparable<E>> E max(Collection<E> c) {
if (c.isEmpty())
throw new IllegalArgumentException("빈 컬렉션");
E result = null;
for (E e : c)
if (result == null || e.compareTo(result) > 0)
result = Objects.requireNonNull(e);
return result;
}
Optional 을 사용한 방식
public static <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
을 반환하는 메서드에서 절대 null 을 반환하지 말아야 한다.Optional
의 도입 취지에 반하는 행위임.Optional.of(value)
에 null 을 넣으면 NPE 가 발생한다.Optional.ofNullable(value)
를 사용해야한다.Optional
을 반환한다.Stream
의 max
연산을 사용하는 경우 Optional
을 쉽게 얻을 수 있다.public static <E extends Comparable<E>> Optional<E> max(Collection<E> c) {
return c.stream().max(Comparator.naturalOrder());
}
Optional<String> maxed = collection.stream().max(String::compareToIgnoreCase);
기본값의 설정
String lastWordInLexicon = max(words).orElse("단어 없음...");
예외 던지기
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
항상 값이 존재한다고 가정하는 경우
Element lastNobleGas = max(Elements.NOBLE_GASES).get();
지연 초기화
orElseGet
을 사용한다.Optional<ExpensiveObject> optionalExpensive = ...;
ExpensiveObject result = optionalExpensive.orElseGet(() -> createExpensiveObject());
orElseGet
을 사용하는데기본값 객체
를 생성하는데, 많은 시간이 소요되는 경우
메모리를 많이 사용하는 큰 객체를 생성해야하는 경우
네트워크 호출이나 DB 조회 등 비용이 많이 드는 연산이 필요한 경우
를 말한다
orElseGet
메서드의 경우 Supplier<T>
를 인수로 받는다.Supplier
를 사용하여 기본값을 생성하기에, 초기 설정 비용을 낮출 수 있다는 장점이 있다.Supplier
는 함수형 인터페이스로서, orElseGet
에서 필요할때( 즉, Optional 이 비어있는 경우에만 ) 기본값을 계산한다.orElse
의 경우에는 Optional 이 비어있든 말든 항상 메서드 호출시점에 이미 평가되는데, 이는 Java 메서드 호출 규칙에 따른 내용이다.orElseGet
은 인자로 함수형 인터페이스를 받기에, Optional 이 없다고 판단된 순간에 지연평가(Lazy evaluation)
을 통하여 필요한 경우에만 기본값 계산 로직이 실행된다. public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
// 비교를 위한 orElse 메서드
public T orElse(T other) {
return value != null ? value : other;
}
// 기존 코드
Optional<ProcessHandle> parentProcess = ph.parent();
System.out.println("부모 PID: " + (parentProcess.isPresent() ?
String.valueOf(parentProcess.get().pid()) : "N/A"));
// 개선된 코드 (map 사용)
System.out.println("부모 PID: " +
ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));
public static void main(String[] args) {
// 샘플 데이터 생성
List<Optional<String>> streamOfOptionals = Arrays.asList(
Optional.of("Hello"),
Optional.empty(),
Optional.of("World"),
Optional.ofNullable(null),
Optional.of("Java")
);
// Java 8 방식
System.out.println("Java 8 방식:");
Stream<String> resultJava8 = streamOfOptionals.stream()
.filter(Optional::isPresent)
.map(Optional::get);
resultJava8.forEach(System.out::println);
// Java 9 이후 방식
System.out.println("\nJava 9 이후 방식:");
Stream<String> resultJava9 = streamOfOptionals.stream()
.flatMap(Optional::stream);
resultJava9.forEach(System.out::println);
}
리스트의 empty 와 Optional 의 empty 의 개념이 중복된다
- 리스트가 비어 있어도 결국 컬렉션 객체는 존재하는데,
- Optional 은 리스트가 비어 있더라도, 객체는 존재하기에, 존재함을 표현하게 된다.
List 가 비어있음을 나타내면 그만이지.. 내부 타입이 비어있다의 중첩된 사고를 해야하기에 상당히 코드 읽기가 피곤하다.