티스토리 뷰
Optional은 null 또는 값을 감싸서 NPE(NullPointerException)로부터 부담을 줄이기 위해 등장한 Wrapper 클래스
Optional의 역할은 더 이해하기 쉬운 API를 설계하도록 돕는 것.
→ 메서드 시그니처만 보고도 선택형 값인지 여부를 구별
- 값을 Wrapping하고 다시 풀고, null 일 경우에는 대체하는 함수를 호출하는 등의 오버헤드가 있으므로 잘못 사용하면 시스템 성능이 저하
- 메소드의 반환 값이 절대 null이 아니라면 Optional을 사용하지 않는 것이 좋다.
- 메소드의 결과가 null이 될 수 있으며, null에 의해 오류가 발생할 가능성이 매우 높을 때 반환값으로만 사용
- 파라미터로 넘어가는 값이 아니라 반환 타입으로써 제한적으로 사용되도록 설계
빈 Optional
정적 팩토리 메서드 Optional.empty() 로 빈 Optional 객체를 반환
Optional<Car> optCar = Optional.empty();
null 이 아닌 값으로 Optional 생성
정적 팩토리 메서드 Optional.of() 로 null 이 아닌 값을 포함하는 Optional 생성
Optional<Car> optCar = Optional.of(car); //car가 null이면 NPE
null 값으로 Optional 생성
정적 팩토리 메서드 Optional.ofNullable() 로 null 값을 저장할 수 있는 Optional 생성
Optional<Car> optCar = Optional.ofNullable(car); //car가 null이면 빈 Optional 반환
Optional.map 값을 추출하고 변환하기
Optional이 값을 포함하면 map의 인수로 제공된 함수가 값을 바꾸고,
Optional이 비어있으면 아무 일도 일어나지 않음
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);
flatMap으로 Optional 객체 연결
값이 존재하면 인수로 제공된 함수를 적용한 결과 Optional을 반환하고,
값이 없으면 빈 Optional을 반환
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar) //Optional<Car>
.flatMap(Car::getInsurance) //Optional<Insurance>
.map(Insurance::getName) //getName
.orElse("Unkown");
}
도메인 모델에 Optional을 사용했을 때 직렬화 불가
Optional은 필드 형식으로 사용할 것을 가정하지 않았으므로 Serializable 인터페이스를 구현하지 않는다.
https://tech.wheejuni.com/2017/12/03/why-no-optional-for-getters/
https://stackoverflow.com/questions/26327957/should-java-8-getters-return-optional-type
Optional 스트림 조작
public Set<String> getCarInsuranceNames(List<Person> persons) {
return persons.stream()
.map(Person::getCar) //Optional<Car> 반환
.map(optCar -> optCar.flatMap(Car::getInsurance)) //Optional<Insurance> 반환
.map(optIns -> optIns.map(Insurance::getName)) //Optional<String> 반환
.flatMap(Optional::stream) //Stream<Optional<String>>을 Stream<String>으로
.collect(toSet());
}
Stream 결과 값에서 빈 Optional 값을 제거하고 언랩 처리
Stream<Optional<String>> stream = ...
Set<String> result = stream.filter(Optional::isPresent) //값이 있는 Optional만 filter
.map(Optional::get) //값으로 언랩
.collect(toSet());
orElse, orElseGet 차이
public String generateValue() {
return "Default Value";
}
Optional<String> optionalValue = Optional.empty();
String value = optionalValue.orElse(generateValue()); //Optional이 있든 없든 항상 호출
System.out.println(value); // 출력: "Default Value"
Optional<String> optionalValue = Optional.empty();
String value = optionalValue.orElseGet(() -> generateValue()); //Optional이 비어있을 때에만 값을 생성하는 Supplier 함수를 호출
System.out.println(value); // 출력: "Generated Value"
값이 존재할 때 수행 ifPresent()
Optional<String> optionalValue = Optional.of("Hello");
optionalValue.ifPresent(value -> System.out.println("Value: " + value)); //Value: Hello
ifPresentOrElse()
Optional<String> optionalValue = Optional.of("Hello");
optionalValue.ifPresentOrElse(
value -> System.out.println("Value: " + value), //출력: "Value: Hello"
() -> System.out.println("Optional is empty") //Optional이 비어있을 때 Runnable 수행
);
두 Optional 합치기
//As-Is
public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person,
Optional<Car> car) {
if (person.isPresent() && car.isPresent()) {
return Optional.of(findCheapestInsurance(person.get(), car.get());
} else {
return Optional.empty();
}
}
//To-Be
public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person,
Optional<Car> car) {
return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
}
filter로 특정값 거르기
Optional 객체가 값을 가지고 predicate와 일치하면 그 값을 반환하고 그렇지 않으면 빈 Optional 객체 반환
Optional 객체가 비어있다면 filter 연산은 아무 동작도 하지 않는다.
//As-is
Insurance insurance = ...;
if (insurance != null && "Cambridge".equals(insurance.getName()) {
System.out.println("ok"));
}
//To-be
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance -> "Cambridge".equals(insurance.getName())
.ifPresent(x -> System.out.println("ok"));
잠재적으로 null이 될 수 있는 대상을 Optional로 감싸기
//As-is
Object value = map.get("key"); //키가 없으면 null 반환
//To-be
Optional<Object> value = Optional.ofNullable(map.get("key"));
예외와 Optional 클래스
Integer.parseInt(string) 을 사용할 때 try/catch 블록을 사용해야하지만
아래와 같이 OptionalUtility.stringToInt 를 이용해서 Optional로 변환하여 사용 가능
public static Optional<Integer> stringToInt(String s) {
try {
return Optional.of(Integer.parseInt(s)); //정수로 변환할 수 있으면 Optional 반환
} catch (NumberFormatException e) {
return Optional.empty();
}
}
기본형 Optional을 사용하지 말아야 하는 이유
OptionalInt, OptionalLong, OptionalDouble 등있지만
Optional의 최대 요소 수는 한 개이므로 기본형 특화 클래스로 성능 개선 없음.
map, flatMap, filter 등 지원하지 않으므로 사용 권장하지 않음.
'Java' 카테고리의 다른 글
[Java] Stream (0) | 2023.07.31 |
---|---|
동작 파라미터화 코드 전달하기 (0) | 2023.06.21 |
[Java] Json 변환 Jackson ObjectMapper (0) | 2022.08.01 |
Primitive vs Wrapper Class (0) | 2022.03.20 |
Functional Programming (0) | 2022.03.14 |