public final class Optional<T> {
private final T value; // T타입의 참조변수
...
}
Optional은 T타입의 객체를 감싸는 래퍼 클래스이다. 그래서 Optional타입의 객체에는 모든 타입의 객체를 담을 수 있다.
최종 연산의 결과를 그냥 반환하지 않고 Optional객체에 담아서 반환하면 반환된 결과가 null인지 매번 if문으로 체크하지 않고 Optional에 정의된 메서드를 통해 간단히 처리할 수 있다.
Optional을 이용하면, 널 체크를 위한 if문없이도 NullPointExceptioin이 발생하지 않아 안전한 코드를 작성할 수 있다.
Optional객체를 생성할 때는 of() 또는 ofNullable()을 사용한다.
String str = "abc";
Optional<String> optVal = Optional.of(str);
Optional<String> optVal = Optional.of("abc");
Optional<String> optVal = Optional.of(new String("abc"));
만일 참조변수의 값이 null일 가능성이 있다면 ofNullable()을 사용한다. of()는 매개변수가 null이면 예외가 발생하기 때문이다.
보통의 경우에서 Optional은 null때문에 사용하기 때문에, of() 보다는 ofNullable() 메서드를 주로 사용한다.
Optional<String> optVal = Optional.of(null); // NullPointException발생
Optional<String> optVal = Optional.ofNullable(null); // OK
Optional타입의 참조변수를 기본값으로 초기화할 때는 empty()메서드를 사용한다.
Optional<String> optVal = null; // 널값으로 초기화 바람직하지 않음
Optional<String> optVal = Optional.<String>empty(); // 빈 객체로 초기화
Optional객체에 저장된 값을 가져올 때는 get()을 사용해 가져온다. 만약 Optional객체에 ofNullable(null)로 값을 저장하고 get()으로 가져오면 NoSuchElementException이 발생하기에 이를 대비하려면 orElse()로 대체할 값을 지정할 수도 있다.
Optional<String> optVal = Optional.of("abc");
String str1 = optVal.get(); // "abc"반환, 만약 null이면 예외 발생
String str2 = optVal.orElse(""); // "abc"반환, 만약 null이면 "" 반환
orElse()의 변형으로는 null을 대체할 값을 반환하는 람다식을 지정할 수 있는 orElseGet()과 null일 때 지정된 예외를 발생시키는 orElseThrow()가 있다.
T orElseGet(Supplier<? extends T> other)
T orElseThrow(Suplier<? extends x> exceptionSupplier)
사용하는 방법은 아래와 같다.
String str3 = optVal2.orElseGet(String::new); // () -> new String()과 동일
String str4 = optVal2.orElseThrow(NullPointException::new); // 널이면 예외 발생
isPresent()는 Optional객체의 값이 null이면 false를, null이면 true를 반환한다. ifPresent(Consumer block)은 값이 있으면 주어진 람다식을 실행하고, 없으면 아무 일도 하지 않는다.
if(str != null)
System.out.println(str);
위 문장을 아래와 같이 Optional과 isPresent()를 사용해 바꿀 수 있다.
if(Optional.ofNullable(str).isPresent()) {
System.out.println(str);
}
위 문장을 ifPresent(Consumer c)를 이용해 더 간단히 바꿀 수 있다.
Optional.ofNullable(str).ifPresent(System.out::println);
기본형 Stream의 최종 연산의 일부는 Optional대신 기본형Optional을 반환한다. 아래는 IntStream에 정의된 메서드들이다.
OptionalInt findAny()
OptionalInt findFirst()
OptionalInt reduce(IntBinaryOperator op)
OptionalInt max()
OptionalInt min()
OptionalInt average()
반환 타입이 Optional이 아닌것을 제외하고는 Stream에 정의된 것과 유사하지만 기본형 Optional에 저장된 값을 꺼낼 때 사용하는 메서드는 이름이 조금씩 다르다.
OptionalInt는 아래와 같이 정의돼 있다.
public final class OptionalInt {
...
private final boolean isPresent; // 값이 저장돼 있으면 true
private final int value; // int타입의 변수
}
int의 기본값이 0이기에 0으로 초기화 되어 .empty()로 만든 OptionalInt와 같은게 아닌가 싶지만 isPresent로 구분이 가능하다.
OptionalInt opt = OptionalInt.of(0);
// OptionalInt에 0을 저장
OptionalInt opt2 = OptionalInt.empyt();
// OptionalInt에 아무것도 저장하지 않지만 0으로 초기화
System.out.println(opt.isPresent()); // true
System.out.println(opt2.isPresent()); // false
System.out.println(opt.equals(opt2)); // false
아래는 Optional의 equals()이다.
@Override
public boolean equals(Object obj) {
if (this == obj) { // ==를 이용하여 같은 주소 값인지 비교.
return true;
}
if (!(obj instanceof OptionalInt)) { // instanceof를 이용하여 같은 타입인지 확인
return false;
}
OptionalInt other = (OptionalInt) obj; // isPresent값을 확인하기 위해 형변환
return (isPresent && other.isPresent)
? value == other.value
: isPresent == other.isPresent;
// isPresent가 둘다 참인지 확인 후 값확인하여 최종적으로 true/false 반환
}