본문 바로가기

책리뷰/모던자바인액션

모던자바인액션 11강 Optional

728x90
반응형

Optional 클래스 소개

java.util.Optional<T> 

Optional은 선택형값을 캡슐화하는 클래스다. 

위의 그림처럼 Car가 null이 아니라 값이 없음을 나타낸다.

Optional 클래스로 감쌀 경우 null이 아닌 Optional.empty 메서드로 Optional을 반환한다.

Optional.empty는 싱글턴 인스턴스를 반환하는 정적 팩토리 메서드다.

 

null은 NullPointerException이 발생하지만 Optional.empty()는 Optional객체이다.

 

그렇다고 모든 Null참조를 Optional로 대치하는 것은 바람직하지 않다.

메서드의 시그니처만 보고 선택형값인지를 구별해서 값이 없을 수 있는 상황에 적절하게 대응하도록 강제하는 효과가 있다.

 

Optional적용 패턴

빈Optional

Optional<Car> optCar = Optional.empty();

 

null값이 아닌 값으로 Optional만들기

Optional<Car> optCar = Optional.of(car);

car가 null이면 NullPointerException이 발생한다.

 

null값으로 Optional만들기

Optional<Car> optCar = Optional.ofNullable(car);

null값을 저장할 수 있는 Optional을 만들 수 있다. car가 null이면 빈 Optional객체가 반환된다.

 

 

 


디폴트 액션과 Optional 언랩

get()

가장 간단한 메서드면서 동시에 가장 안전하지 않은 메서드.

래핑된 값이 있으면 반환하고 없으면 NoSuchElementException 을 발생시킨다.

반드시 값이 있는 상황이 아니면 사용하지 않는 것이 바람직하다. 

 

orElse()

Option이 값을 포함하지 않을 때 기본 값을 제공할 수 있다.

 

orElseGet()

orElse메서드에 대응하는 게으른 버전의 메서드.

Optional에 값이 없을 때만 Supplier가 실행된다. 

기본값이 반드시 필요한 상황에 사용해야한다.

 

orElseThrow()

Optional이 비었을 때 예외를 발생시킨다. get과 비슷하다. 

발생시킬 예외의 종류를 선택할 수 있다.

 

ifPresent(Consumer<? super T> consumer)

값이 존재할 때 인수로 넘겨준 동작을 실행할 수 있다. 값이 없으면 아무일도 일어나지 않는다.

 

ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction)

자바9에서 추가되었다.

Optional이 비었을 때 실행할 수 있는  Runnable을 인수로 받는다는 점이 ifPresent와 다르다. 

 


Optional 사용예제

optional을 언랩하지 않고 두 Optional을 연결

public Insurance findCheapestInsurance(Person person, Car car) {
	return cheapestCompany;
}

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();
	}
}

위의 nullSafeFindCheapestInsurance는 기존의 null체크와 다르지 않다.

위 코드를 한 줄로 줄일 수 있다.

public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person, Optional<Car> car){ 
	return person.flatMap(p -> car.map(c -> findCheapestInsurance(p, c)));
}

Optional에 flatMap을 호출했을 때 Optional이 비어있으면 람다 표현식이 실행되지 않고 빈 Optional을 반환한다.

person에 값이 있으면 flatMap에서 필요한 Optional<Insurance>를 반환하는 Function의 입력이므로 person을 사용한다. 

함수 바디에서 두 번째 Optional에 map을 호출하는데 Optional이 car를 포함하지 않으면 Function은 빈 Optional을 반환하기 때문에 nullSafeFindCheapestInsurance는 빈 Optional을 반환한다. 

마지막으로 Person와 Car 모두 존재하면 map메서드로 전달한 람다표현식이 findCheapestInsurance메서드를 안전하게 호출한다. 


 

Optional map vs flatMap


import org.junit.Test;

import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.util.Arrays.asList;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class OptionalTest {

    @Test
    public void optionalTest() {
        Optional<String> s = Optional.of("test");
        assertEquals(Optional.of("TEST"), s.map(String::toUpperCase));
    }

    @Test
    public void optionalTest2() {
        assertEquals(Optional.of(Optional.of("STRING")),
                Optional
                        .of("string")
                        .map(s -> Optional.of("STRING")));
    }

    @Test
    public void optionalTest3() {
        assertEquals(Optional.of("STRING"), Optional
                .of("string")
                .flatMap(s -> Optional.of("STRING")));
    }

    @Test
    public void optionalListTest1() {
        List<String> myList = Stream.of("a", "b")
                .map(String::toUpperCase)
                .collect(Collectors.toList());
        assertEquals(asList("A", "B"), myList);
    }

    @Test
    public void optionaListTest2() {
        List<List<String>> list = Arrays.asList(
                Arrays.asList("a"),
                Arrays.asList("b"));
        System.out.println(list);
        System.out.println(list
                .stream()
                .flatMap(Collection::stream)
                .collect(Collectors.toList()));

        //  출력
        // [[a], [b]]
        // [a, b]
    }
}

https://www.baeldung.com/java-difference-map-and-flatmap

// 메서드 시그니처
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper);
// 예제
String result = Optional.of("result")
        .flatMap((val) -> Optional.of("good"))
        .get();
System.out.println(result); // print 'good'

Optional 메서드


Null인 부분 Optional로 변경하기

Object value = map.get("key");

key에 해당하는 값이 없으면 null을 리턴한다. Optional로 감싸서 개선할 수 있다. 

Optional<Object> value = Optional.ofNullable(map.get("key"));

 

문자열을 정수 Optional로 변환

public static Optional<Integer> stringToInt(String s) {
	try {
    	return Optional.of(Integer.parseInt(s));
    } catch(NumberFormatException e) {
    	return Optional.empty();
    }
}

 

728x90
반응형