Lee's Grow up

[자바/Java] Optional 개념, 사용 본문

PROGRAMMING/JAVA

[자바/Java] Optional 개념, 사용

효기로그 2020. 6. 3. 13:06
반응형

해당 내용은 책 모던 자바 인 액션의 내용을 참고해서 작성하였습니다.

등장 배경

Java를 통해 개발을 진행하다 보면, 해당 객체의 참조 값이 null인지 아닌지 체크하는 분기문이 생기는데, 이와 같은 분기문의 경우 코드의 가독성이 떨어지고, 해당 객체가 null을 가질 수 있는 객체인지, 필수 값인지 직관적으로 알 수 있는 방법이 없어서 에러의 근원이 되는 문제가 발생했기 때문에, 선택형값을 캡슐화하는 클래스 Optional을 Java 8에 추가되었다.

Optional 객체 만들기

1. 빈 Optional 객체 만들기

Optionl 클래스의 정적 팩토리 메소드로 아래와 같이 사용 가능

Optional<String> optStr = Optional.empty();
2. null이 아닌 값으로 Optional 만들기

만약 값이 null이라면 바로 NullPointException이 발생하며, 값을 항상 넘겨줘야 한다.

Optional<String> optStr = Optional.of("not null");
3. null 허용 Optional 만들기

null또는 값을 가질 수 있는 Optional을 리턴해준다.

Optional<String> optStr = Optional.ofNullable(null);

Optional 클래스의 메서드

예제를 위해 사용되는 클래스는 아래와 같다.

public class Person {

    private Car car;
    private int age;

    public Optional<Car> getCar(){ return Optional.ofNullable(car); }
    public int getAge() { return age; }
}

public class Insurance {

    private String name;

    public String getName() { return name; }
}

public class Car {

    private Insurance insurance;

    public Optional<Insurance> getInsurance(){ return Optional.ofNullable(insurance); }
}

위와 같은 도메인 모델이 구성되어 있을 때, Car을 기준으로 보면 getCar은 Optional을 리턴하고 getAge는 int형을 리턴해준다. 이렇게 한눈으로 값이 꼭 있어야 하는지, null이 들어갈 수 있는 객체인지 여부를 구체적으로 표현할 수 있는 장점이 있게된다. 또 특이한점은 필드로 옵셔널 객체 즉 private Optional<Car> car 과 같은 방식을 사용하지 않은 이유는 해당 클래스를 설계한 사람이 용도를 선택형 반환값으로 구분을 지었기 때문에 Serializable를 구현하지 않았다. 그래서 직렬화가 필요한 프레임워크나, 도구에서 문제가 생길 수가 있기 때문이다.

메서드의 종류
1. empty, of, ofNullable

Optional 인스턴스를 반환 각각 빈 옵셔널, 널이 아닌 옵셔널, 널을 허용하는 옵셔널 ( 위에 설명이 있기 때문에 생략 )

2. filter

마치 스트림처럼 피리디케이트와 일치하면 값을 포함한 Optional<T>을 리턴, 없으면 empty 옵셔널을 리턴해준다.

Optional.of(new Person()).optPerson.filter(p -> p.getAge() > 19);

위와 같이 사용가능하며, 나이가 19세를 넘어가는 Person인지 필터가 가능하다.

3. map

스트림과 비슷하며, 존재하면 값이 존재하면 매핑 함수를 적용

Optional<String> name = 
                Optional.of(new Insurance()).map(Insurance::getName);

Optional<Optional<Car>> optCar = 
                Optional.of(new Person()).map(Person::getCar);

2개의 map을 사용했으며, optCar의 경우 반환 타입이 옵셔널 안에 옵셔널이 감싸지는 형태로 리턴이 된다. 이유는 Optional.map 자체가 해당 값을 옵셔널로 감싸주는 메서드인데, Person.getCar의 경우 리턴 타입이 Optional<Car>이기 때문이다. 이와 같은 문제로 인해, 바로 아래 flatMap 메서드를 제공해준다.

4. flatMap

스트림과 비슷, 평면화를 위해 사용. 값이 존재하면 제공된 함수를 적용한 결과를 값을 포함한 Optional<T>을 리턴, 없으면 empty 옵셔널을 리턴해준다.

Optional<Insurance> optInsurancer = 
                Optional.of(new Person())
                        .flatMap(Person::getCar)
                        .flatMap(Car::getInsurance);
5. get

Optional이 감싸고 있는 값을 반환하고, 값이 없으면 NoSuchElementException 발생

Optional.empty().get();

위에는 빈 옵셔널에 get메서드를 호출하기 때문에 NoSuchElementException이 발생한다.

6. isPresent

값이 존재하면 true, 빈 옵셔널이면 false

Optional.ofNullable(null).isPresent();
Optional.ofNullable("not null").isPresent();

위 첫번 째 결과는 false, 두번째는 true를 반환

7. ifPresent ,ifPresentOrElse

ifPresent의 경우 값이 존재하면 지정된 Consumer를 실행하고, 없으면 아무동작도 안함
ifPresentOrElse은 java 9에 추가 되었으며, 추가로 Runnable 인터페이스를 넘겨준다. 값이 존재하면 지정된 Consumer를 실행하고, 값이 없으면 넘겨받은 Runnable를 실행

// 첫번째 실행 안하고 not null만 출력
Optional.ofNullable(null).ifPresent(System.out::println);
Optional.ofNullable("not null").ifPresent(System.out::println);

// 무언가동작, not null 2개의 문장 출력
Optional.ofNullable(null).ifPresentOrElse(System.out::println, () -> System.out.println("무언가 동작"));
Optional.ofNullable("not null").ifPresentOrElse(System.out::println, () -> System.out.println("무언가 동작"));
8. or

값이 존재하면 같은 Optional<T>을 리턴, 없으면 Supplier에서 만든 Optional<T>을 리턴

Optional.ofNullable(null).or(() ->(Optional.of("UnKnown")));
Optional.ofNullable("not null").or(() ->(Optional.of("UnKnown")));

각각 Optional<String>를 리턴해주며, 첫번째는 "UnKnown"이라는 값을 가진 옵셔널을 두번째는 "not null"이란 값을 가진 옵서녈을 리턴해준다.

9. orElse

값이 존재하면 값을 리턴, 없으면 인자로 제공된 기본값을 반환

Optional.ofNullable(null).orElse("UnKnown");

or 메서드와 다르게 바로 값을 반환해주며 해당 예제는 String 타입의 "UnKnown"을 리턴해준다

10. orElseGet

값이 존재하면 값을 리턴, 없으면 Supplier에서 제공되는 값을 반환

Optional.ofNullable(null).orElseGet(() -> new String("UnKnown"));
11.orElseThrow

값이 존재하면 값을 리턴, 없으면 Supplier에서 생성한 예외를 발생

Optional.ofNullable(null).orElseThrow(NullPointerException::new);

예제에서는 NullPointException을 사용했는데, 이러면 Optional을 사용하는 의미가 없기 때문에, 필요하다면 사용자 정의 Exception 또는 기존에 정의된 Exception중 필요한걸 찾아서 넘겨주면 된다.

12. stream

값이 존재하면 존재하는 값만 포함하는 스트림을 반환하고, 없으면 empty 스트림 반환

Optional.ofNullable(null).stream();
기본형 특화 옵셔널

스트림처럼 기본형 int, long, double 전용 옵셔널이 존재하지만. 스트림의 경우 요소가 여러개이기 때문에 오토박싱에 대한 비용을 감소시켜서 성능 향상의 효과가 있었지만, 옵셔널의 경우 요소의 수는 한개이므로 성능향상은 존재하지 않는다. 또한 map, flatMap, fillter 등을 지원해주지 않고, 기존의 Optional과 혼용으로 사용이 불가능 하기 때문에 기본형 특화 옵셔널 사용을 하지 말아야 한다.

반응형
Comments