티스토리 뷰

Java

Optional

snail voyager 2022. 9. 18. 18:19
728x90
반응형
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 등 지원하지 않으므로 사용 권장하지 않음.

728x90
반응형

'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
반응형
300x250