Optional
Optional<T> 객체는 T타입 객체 또는 객체가 없는 경우의 래퍼다. T타입일수도있고 null일수도있을때 쓰는 표현이다.
optional은 객체 또는 null을 가리키는 T타입 레퍼런스보다 안전한 대안으로 만들어졌다.
하지만 올바르게 사용할 경우에만 안전하다.
(Optional을 제대로 사용하려면, Optional을 최대 1개의 원소를 가지고 있는 특별한 Stream이라고 생각하면 편하다.)
get메서드는 감싸고있는 요소가 존재할때만 요소를 얻고, 그렇지않으면 NoSuchElementException을 던진다.
Optional<T> val = ...;
val.get().someMethod(); // <- T가 null이면 NoSuchElementException예외 발생
T val = ...; // 위의 방식대로 사용하면 기존방식보다 나은게 없다.
T.someMethod(); // T가 null이면 어차피 여기서도 NPE예외가 발생할테니..
isPresent메서드는 optional 객체가 값을 포함하는지 알려준다.
if (val.isPresent()){ // 그렇다면 이렇게 체크해주면 되지않을까?
val.someMethod();
}
if(val != null){ // 위의방식대로한다면 기존방식보다 어려워보이기만하고 나을게 전혀없다.
val.someMethod();
}
옵션 값 다루기
optional을 위의 방식대로쓴다면 쓰지않는편이 낫다.
optional을 효과적으로 사용하는 핵심은 올바른 값을 소비하거나 대체값을 생산하는 메서드를 사용하는것이다.
(패러다임 자체를 바꿔야함)
- 올바른값을 소비
ifPresent메서드는 옵션값이 존재하면 해당함수로 전달되며, 그렇지않으면 아무일도 일어나지않는다. if문대신 이 메서드로 처리가 가능하다.val.ifPresent(v -> v.처리); val.ifPresent(v -> list.add(v)); // 리스트에 해당값을 추가할때 val.ifPresent(list::add); // ifPresent는 값을 리턴하지않는다. 리턴값을 처리하고싶을때는 ifPresent대신 map사용 Optional<Boolean> added = val.map(list::add);
- 대체값을 생산
String result = val.orElse(""); // optional 값이 있으면 그값, null이라면 ""값 리턴 (값이 있다면 .get()과 같은결과) String result = val.orElseGet(() -> System.getProperty("user.dir")); // optional 값이 있으면 그값, null이라면 디폴트를 계산 후 리턴 String result = val.orElseThrow(NoSuchElementException::new); // 값이없을때 예외를 던짐
- 옵션값이 존재할때 소비하는 방법말고도 옵션값이 없을때 대체값을 만드는 방법 또한 존재한다.
옵션 값 생성하기
위는 다른 누군가가 생성한 optional 객체를 소비하는 방법이다. 하지만 of(), empty()를 이용해 optional객체를 생성할 수 있다.
ofNullable메서드는 null값 사용을 옵션값 사용으로 이어주는 용도이다. Optional.ofNullable(obj)는 obj가 null이면 Optional.empty()를 리턴하고 null이 아니라면 Optional.of()를 리턴한다.
of, empty를 각각 따로 쓸 수 있지만, ofNullable을 쓰면 2개를 묶어서 안전하게 사용할수있기때문에 optional객체를 생성할때는 ofNullable을 쓰는게 낫다
public static Optional<Double> inverse(Double x){
return x == 0 ? Optional.empty() : Optional.of(1 / x);
}
flatMap을 이용해 옵션 값 합성하기
Optional<T>를 리턴하는 f메서드가 있고, T는 Optional<U>를 리턴하는 g메서드를 포함하고있다고 가정하자.
일반메서드라면 s.f().g() 를 호출하는 방법으로 이 메서드들을 합성할 수 있다. 하지만 이 경우에는 s.f()가 T가 아닌 Optional<T> 타입을 리턴하므로 이러한 합성방식으로는 동작하지않는다.
대신 다음과 같이 호출한다.
Optional<U> result = s.f().flatMap(T::g); // s.f()로 리턴되는 Optional<T>가 null이 아니면 T의 g메서드를 실행 / null이라면 비어있는 Optional<U>객체를 리턴
// 이 과정을 반복하면 모든 부분이 성공할 경우에만 성공하는 단계들의 파이프라인을 구축할 수 있다.
public static Optional<Double> sqareRoot(Double x){
return x == 0 ? Optional.empty() : Optional.of(Math.sqrt(x));
}
// 역수의 루트계산
Optional<Double> result = inverse(x).flatMap(Test::sqareRoot); // inverse(x)의 결과가 비어있지않으면 Test클래스의 sqareRoot를 실행
// inverse나 sqareRoot중 하나라도 Optinal.empty()를 리턴하며, 결과는 비어있다.
Optional<Double> result = inverse(5.0).map(Main::sqareRoot);
// Optional<Optional<Object>> 를 Optional<Object>로 바꿔주는데 사용. 풀어서 하나로 합친다고 생각하면됨
참고
'IT > 자바' 카테고리의 다른 글
classpath (0) | 2023.03.26 |
---|---|
ExecutorService (0) | 2023.03.26 |
Java8 람다(Lambda)를 이용한 프로그래밍 (0) | 2022.01.23 |
Java8 스트림(Stream)연산을 사용해보자 (0) | 2022.01.23 |
Java8 람다(Lambda)를 사용해보자 (0) | 2022.01.23 |
댓글