[Java] - Optional

janjanee·2021년 5월 6일
0

Java

목록 보기
4/17
post-thumbnail

인프런 더 자바, Java 8 - 백기선 강의를 듣고 정리한 내용

Optional

오직 값 한개가 들어올 수도 없을 수도 있는 컨테이너. (래퍼 클래스)

  • NullPointerException을 보게 되는 이유?

    • null을 리턴 && null 체크를 깜빡
  • 메소드 작업 중 특별한 상황에서 값을 제대로 리턴할 수 없는 경우 선택 방법

    • 예외를 던진다. (비용이 비싸다. 스택 트레이스 찍으니까)
    • null을 리턴한다. (비용 문제는 없지만, 해당 코드를 사용하는 클라이언트 코드가 주의해야함.)
    • (자바 8부터) Optional을 리턴한다.
      • 클라이언트에게 명시적으로 빈 값일 수도 있다는걸 알려주고, 빈 값인 경우에 대한 처리를 강제화

Optional 객체 생성

String str = "apple";
Optional<String> opt1 = Optional.of(str);
Optional<String> opt2 = Optional.ofNullable(str);
Optional<String> opt3 = Optional.empty();
  • of() 또는 ofNullable() 사용
  • 참조변수의 값이 null일 가능성이 있으면 of() 대신 ofNullable()을 사용
  • 기본값으로 초기화 할 때는 empty()를 사용한다.

Optional API

isPresent() / isEmpty()

List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1, "spring boot", true));
springClasses.add(new OnlineClass(5, "rest api development", false));

Optional<OnlineClass> optional = springClasses.stream()
				.filter(oc -> oc.getTitle().startsWith("spring"))
				.findFirst();

boolean present = optional.isPresent();

스트림을 사용하여 제목이 "spring"으로 시작하는 것을 filter하고

그 중 첫번째로 나온것을 반환하는데 이 때 반환타입이 Optional이다.

필터된 값이 있을 수도 없을 수도 있기 때문이다.

  • isPresent() : 반환된 값이 존재하는지 확인한다.

  • isEmpty() : 반대로 반환된 값이 존재하지 않는지 확인한다.

get()

List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1, "spring boot", true));
springClasses.add(new OnlineClass(5, "rest api development", false));

Optional<OnlineClass> optional = springClasses.stream()
        .filter(oc -> oc.getTitle().startsWith("spring"))
        .findFirst();

OnlineClass onlineClass = optional.get();
  • Optional에 있는 값을 가져오려면 get()을 이용한다.

  • startsWith("java")인 경우, Optional은 "java"로 시작하는 값이 없기 때문에
    빈 값을 갖고 있을텐데 비어있는 Optional에 get()을 하면?
    → NoSuchElementException 발생!!!

뒤이어 나올 다른 API를 통해서 위의 문제 상황을 처리할 수 있다.

ifPresent(Consumer)

List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1, "spring boot", true));
springClasses.add(new OnlineClass(5, "rest api development", false));

Optional<OnlineClass> optional = springClasses.stream()
		.filter(oc -> oc.getTitle().startsWith("spring"))
		.findFirst();

optional.ifPresent(oc -> System.out.println(oc.getTitle()));
  • ifPresent는 Optional에 값이 있는 경우 특정 처리를 할 수 있게 한다.

  • 특정 처리와 관련된 내용은 Consumer 함수형 인터페이스 람다식으로 작성.

orElse(T)

public class App {

    public static void main(String[] args) {
        List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(5, "rest api development", false));

        Optional<OnlineClass> optional = springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("java"))
                .findFirst();

        OnlineClass onlineClass = optional.orElse(createNewClass());
        System.out.println(onlineClass.getTitle());
    }

    private static OnlineClass createNewClass() {
        System.out.println("creating new class");
        return new OnlineClass(10, "new class", false);
    }

}
  • orElse()를 사용하면 Optional이 빈 값일 경우 createNewClass()를 호출하여 새로운 OnlineClass로 반환한다.
  • 그런데 orElse()는 값이 있든 없든 메소드 자체를 무조건 호출하는데,
    • 예를 들어 startsWith("spring")이면 Optional이 비어있지 않음에도 불구하고
      createNewClass()가 호출된다. "creating new class"가 계속 찍히는 것을 확인.
    • 위의 상황이 마음에 들지 않는다면 바로 다음에 이어 나오는 orElseGet() 을 사용하자.

orElseGet(Supplier)

public class App {

    public static void main(String[] args) {
        List<OnlineClass> springClasses = new ArrayList<>();
        springClasses.add(new OnlineClass(1, "spring boot", true));
        springClasses.add(new OnlineClass(5, "rest api development", false));

        Optional<OnlineClass> optional = springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .findFirst();

        OnlineClass onlineClass = optional.orElseGet(App::createNewClass);
        System.out.println(onlineClass.getTitle());
    }

    private static OnlineClass createNewClass() {
        System.out.println("creating new class");
        return new OnlineClass(10, "new class", false);
    }

}
  • orElseGet()을 사용하면 값이 있는 경우에 Supplier(App::createNewClass)를 실행하지 않는다.

orElseThrow()

List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1, "spring boot", true));
springClasses.add(new OnlineClass(5, "rest api development", false));

Optional<OnlineClass> optional = springClasses.stream()
		.filter(oc -> oc.getTitle().startsWith("jpa"))
		.findFirst();

OnlineClass onlineClass = optional.orElseThrow();
  • 값이 없을 경우 예외를 던질 수도 있다.
  • 기본은 NoSuchElementException인데 원하는 예외가 있다면 Supplier로 제공해주면 된다.

filter(Predicate)

List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1, "spring boot", true));
springClasses.add(new OnlineClass(5, "rest api development", false));

Optional<OnlineClass> optional = springClasses.stream()
		.filter(oc -> oc.getTitle().startsWith("spring"))
		.findFirst();

Optional<OnlineClass> optional2 = optional.filter(OnlineClass::isClosed);
  • Optional에 있는 값을 filter 할 수 있다. 리턴값 역시 Optional이다.

map(Function) / flatMap(Function)

List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1, "spring boot", true));
springClasses.add(new OnlineClass(5, "rest api development", false));

Optional<OnlineClass> optional = springClasses.stream()
        .filter(oc -> oc.getTitle().startsWith("spring"))
        .findFirst();

Optional<Integer> optional2 = optional.map(OnlineClass::getId);
  • map()을 이용하여 optional에 들어있는 값을 변환할 수 있다.

  • 예제의 타입이 OnlineClass → Integer로 바뀐것을 확인할 수 있다.

flatMap()


List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1, "spring boot", true));
springClasses.add(new OnlineClass(5, "rest api development", false));

Optional<OnlineClass> optional = springClasses.stream()
        .filter(oc -> oc.getTitle().startsWith("spring"))
        .findFirst();

//  map
Optional<Optional<Progress>> progress = optional.map(OnlineClass::getProgress);
Optional<Progress> progress1= progress.orElse(Optional.empty());

//  flatMap
Optional<Progress> progress2 = optional.flatMap(OnlineClass::getProgress);
  • flatMap의 경우 Optional안에 들어있는 인스턴스가 Optional인 경우 사용하면 편리한 api이다.

  • flatMap을 사용하면 map을 사용할 경우보다 작업 단계가 한 단계 더 줄어든다고 보면 된다.

OptionalInt, OptionalLong, OPtionalDouble

OptionalInt optionalInt = OptionalInt.of(10);
OptionalInt optionalInt1 = OptionalInt.empty();
System.out.println(optionalInt.isPresent());
System.out.println(optionalInt1.isPresent());
System.out.println(optionalInt.getAsInt());
  • Optional도 Stream과 마찬가지로 기본형 Optional이 제공된다.
  • 값을 꺼낼 때 get()이 아닌 getAsInt(), getAsLong(), getAsDouble()이다.

주의할 점

  • Optional은 리턴값 으로만 쓰기를 권장.
    • 만들어진 목적 자체가 메소드 리턴타입으로 쓸 용도
    • 메소드 매개변수 타입 : 인자로 null을 넣으면 메소드 내부에서 null check를 또 해주어야 한다.
    • 맵의 키 타입 : 맵의 키는 null 일 수 없다는 인터페이스 특징 자체를 깨트림.
    • 인스턴스 필드 타입 : 어떤 클래스에 인스턴스 필드가 있을 수도 있고 없을 수도 있고???
      필드가 있을 수도 없을 수도 있다는 자체가 도메인 클래스 설계의 문제일 수 있다.
  • Optional을 리턴하는 메소드에서 null을 리턴하지 말자.
    • 정말 리턴할 게 없다면 Optional.empty() 를 사용하자.
  • Collection, Map, Stream, Array, Optional은 Optional로 감싸지 말 것.

References

profile
얍얍 개발 펀치

0개의 댓글