-
Optional - 모던 자바 인 액션BackEnd/자바 2022. 4. 4. 23:48
자바8은 하스켈과 스칼라의 영향을 받아 Optional 클래스를 제공한다. 선택형값을 캡슐화하는 클래스이다.
null 참조와 Optional.empty() 의 차이
null 을 참조하려 하면 NPE 이 발생하지만 Optional 은 객체라서 활용할 수 있다.
null 대신 Optional 을 사용하면 값이 없을 수도 있음을 명시적으로 보여준다.
반면 객체 자체를 사용하면 null 참조가 할당될 경우 이것이 올바른 값인지 잘못된 값인지 판단할 정보가 없다.
아래는 Person 이 Car 를 소유할 수도 있고 소유하지 않을 수도 있음을 명시적으로 보여준다.
도메인 모델의 의미를 더 명확하게 만들 수 있는 것을 보여준다.public class Person { private Optional<Car> car; public Optional<Car> getCar() { return car; } }
Optional.empty() : 빈 Optional 객체를 얻을 수 있다.
Optional.of(obj) : null 이 아닌 객체를 포함하는 Optional 을 만들 수 있다.
Optional.ofNullable(obj) : null 이 될 수도 있는 객체를 포함하는 Optional 을 만들 수 있다.
Optional.get 으로 null 인 객체를 꺼내려하면 NPE 가 발생한다. 그럼 null 을 사용할 때와 동일한 문제가 발생하는데,
명시적인 null 검사를 생략하려면, Optional 의 map 메서드를 사용하면 된다.
스트림의 map 은 스트림의 각 요소에 제공된 함수를 적용하는 연산이다. 여기서 Optional 객체를 최대 요소의 개수가 한 개 이하인 데이터 컬렉션으로 생각할 수 있다.
Optional 이 값을 포함하면 map 의 인수로 제공된 함수가 값을 바꾸고 Optional 이 비어있으면 아무 일도 일어나지 않는다.Optional<Insurance> optIns = Optional.ofNullable(insurance); Optional<String> name = optIns.map(Insurance::getName);
여러개의 메서드를 호출할 때는 아래와 같이 구현이 가능할 것 같지만 아래 코드는 2번째 줄부터 컴파일 오류가 난다.
optPerson 이 Optional 로 감싸져 있는데 getCar을 하면 Optional<Optional<Car>> 형식의 객체가 반환되기 때문이다.Optional<Person> optPerson = Optional.of(person); optPerson.map(Person::getCar) .map(Car::getInsurance) .map(Insurance::getName);
flatmap 을 대신 사용한다. flatmap 은 인수로 받은 함수를 적용해서 생성된 각각의 스트림에서 컨텐츠만 남긴다.
즉, 함수를 적용해 생성된 모든 스트림이 하나의 스트림으로 병합되어 평준화된다.
이차원 Optional 을 일차원 Optional 로 평준화하는 것이다.public String getCarInsuranceName(Optional<Person> optPerson){ return optPerson.flatMap(Person::getCar) .flatMap(Car::getInsurance) .map(Insurance::getName) .orElse("Unknown"); }
이런 방식을 null 을 확인하느라 조건 분기문을 추가해 코드를 복잡하게 만들지 않으면서 쉽게 이해할 수 있다.
또한 Optional 을 사용하므로 도메인 모델과 관련한 암묵적인 지식에 의존하지 않고 명시적으로 형식 시스템을 정의할 수 있다. Optional 을 인수로 받거나 Optional 을 반환하는 메서드를 정의한다면 결과적으로 이 메서드를 사용하는 모든 사람에게 이 메서드가 빈 값을 받거나 빈 값을 반환할 수 있음을 잘 문서화해서 제공하는 것과 같다.
Optional 은 Serializable 인터페이스를 구현하지 않아, 도메인 모델에 Optional 을 사용하면 직렬화 할 수 없다.
그래도 객체 그래프에서 일부 또는 전체가 null 일 수 있는 상황이라면 Optional 을 사용하는 것이 좋다.
직렬화 모델이 필요하다면 Optional 값을 반환받을 수 있는 메서드를 추가하는 방식을 권장한다.public class Person2 { private Car car; public Optional<Car> getCarOptional() { return Optional.ofNullable(car); } }
Optional Stream 조작
Optional 의 stream() 메서드는 Optional 스트림을 값을 가진 스트림으로 변환할 때 유용하다.
아래 체인에서 Optional::stream 을 사용하지 않았다면 filter(Optional::isPresent).map(Optional::get).collect.. 를 구현해야한다.
Optional 언랩
get은 가장 간단한 메서드지만 가장 안전하지 않은 메서드다. 요소가 없으면 NoSuchElementException 을 발생시켜 null 을 넣는 상황과 크게 다르지 않다.
orElse 는 Optional 이 값을 포함하지 않을 때 기본값을 제공한다.
orElseGet(Supplier) 는 orElse 메서드에 대응하는 게이른 버전의 메서드다. Optional 에 값이 없을 때만 Supplier 가 실행된다.
디폴트 메서드를 만드는 데 시간이 걸리거나 Optional 이 비었을 때만 기본값을 생성하고 싶다면 이를 사용해야한다.
orElseThrow 값이 없으면 exception 을 발생시키는 점에서 get과 같지만, exception 을 선택할 수 있다는 점에서 다르다.
ifPresent(Consumer) 값이 존재할 때 supplier 동작을 실행하고 값이 없으면 아무일도 하지 않는다.
ifPresentOrElse(Consumer,Runnable) Optional 이 비었을 때 실행할 Runnable 을 인수로 받는다.
잠재적 null 감싸기
Optional<Object> value = Optional.ofNullable(map.get("key"));
예외와 Optional 클래스
Integer.parseInt(String) => 변환 불가 시 NumberFormatException 발생
유틸리티 메서드를 구현해 Optional 을 반환public static Optional<Integer> stringToInt(String s){ try { return Optional.of(Integer.parseInt(s)); } catch (NumberFormatException e){ return Optional.empty(); } }
기본형 특화 Optional 의 사용 지양
OptionalInt, OptionalLong,OptionalDouble 등 기본형 특화 Optional 이 있는데,
Stream 에서는 기본형 Stream 을 사용하는 것이 성능 개선을 해주었지만, Optional 의 요소는 한개로 제한되어 있어
성능 개선과 관련이 없으며, 기본형 Optional 에는 map, flatmap, filter 등을 지원하지 않는다.
또 스트림과 마찬가지로 기본형 특화 Optional 로 생성한 결과는 다른 일반 Optional 과 혼용할 수 없다.
public int readDuration(Properties props, String name){ Optional<String> value = Optional.ofNullable(props.getProperty(name)); return value.flatMap(Main::stringToInt) .filter(i -> i > 0).orElse(0); }
반응형'BackEnd > 자바' 카테고리의 다른 글
Date - 모던 자바 인 액션 (0) 2022.04.09 Optional 에서 flatmap 과 map (0) 2022.04.07 컬렉션 - 모던 (0) 2022.03.21 스트림의 병렬 처리 - 모던 자바 인 액션 (0) 2022.03.20 collector 커스터마이징 - 모던 자바 인 액션 (0) 2022.03.10