nonosa
그냥저냥끄적끄적
nonosa
  • 분류 전체보기 (31)
    • 자바 (15)
      • 백기선 자바스터디 (11)
      • 백기선 더 자바, java8 (4)
    • 스프링 (0)
    • CS (15)
      • 자료구조 (8)
      • 운영체제 (6)
      • 데이터베이스 (0)
      • 운영체제 문제풀이 (0)
      • 컴퓨터아키텍쳐 (1)
    • 코딩테스트 (1)
    • 기타 (0)

인기 글

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
nonosa

그냥저냥끄적끄적

[더 자바, java8] Optional 소개
자바/백기선 더 자바, java8

[더 자바, java8] Optional 소개

2022. 11. 20. 16:11

본 게시물은 인프런 백기선님 강의 "더 자바, java8"을 학습하고 개인적으로 정리한 내용입니다.

 

https://www.inflearn.com/course/the-java-java8#

 

더 자바, Java 8 - 인프런 | 강의

자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이

www.inflearn.com

목차

1. Optional 소개

2. Optional API


1. Optional 소개

Optional 배경

App.java

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(2,"spring data jpa", true));
        springClasses.add(new OnlineClass(3,"spring mvc", false));
        springClasses.add(new OnlineClass(4,"spring core", false));
        springClasses.add(new OnlineClass(5,"rest api development", false));

        OnlineClass spring_boot = new OnlineClass(1,"spring boot", true);
        Duration studyDuration = spring_boot.getProgress().getStudyDuration();
        System.out.println(studyDuration);
    }
}

 

Progress.java

public class Progress {
    private Duration studyDuration;
    private boolean finished;

    public Duration getStudyDuration() {
        return studyDuration;
    }
    public void setStudyDuration(Duration studyDuration) {
        this.studyDuration = studyDuration;
    }
}

 

Duration studyDuration = springboot.getProgerss().getStudyDuration(); 에서

nullPointException이 일어난다. progress 기본값이 null이고, 아무것도 할당하지 않았기 때문이다.

 

그래서 다음과 같이 예외처리를 할 수 있는데

 

Progress progress = spring_boot.getProgress();
if (progress != null) {
    System.out.println(progress.getStudyDuration());
}

 

위와 같이 예외처리하면 매번 null 체크를 할 수 없기 때문에 사람이라면 실수가 나올 수 밖에 없다.

 

java8부터는 다음과 같이 null이 전달될 수 있는 경우를 대비해 반환값을 Optional로 감싼다.

 

Progress progress = spring_boot.getProgress();
if (progress != null) {
    System.out.println(progress.getStudyDuration());
}

 

즉 Optional은 오직 값 한개가 들어있을 수도, 없을 수도 있는 컨테이너이다.

 

주의할 것

1. return 타입으로만 사용할 것

 

문법적으로는 제한은 없지만 리턴 타입으로만 사용한다. 메서드의 파라미터에 Optional이 오는 경우를 생각하면, 그 Optional이 null인지 Optional인지 검사해야하는데, 중복으로 null을 검사하게 되므로, Optional의 취지에 어긋난다. map의 key type을 Optional로 쓰는 것 또한 같은 맥락으로 위험하다. 

 

2. 프리미티브 타입용 Optional을 사용하자.

 

Optional.of(10)의 경우 오토박싱 언박싱이 일어나기 때문에 성능저하가 발생한다.

OptionalInt.of(10)과 같이 프리미티브 타입용 Optional을 사용하자.

 

3. null을 return하지 말자

 

Optional의 취지에 어긋난다.

//bad case
public Optional<Progress> getProgress() {
    return null;
}

//better case
public Optional<Progress> getProgress() {
    return null;
}

 

4. Collections, Map, Stream Array, Optional을 Optional로 감싸지 말자

 

위의 type들은 비어있다는 것 자체를 표현할 수 있는 type들이다. 그 자체로 비어있는지 판단할수 있는 container를 Optional로 감싸는 것은 두번 중복해서 감싸는 것과 같다.


Optional API

다음 코드와 같이 findFirst()를 사용하면 Optional을 return한다. 값이 null일 수도 있기 때문이다.

 

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> spring = springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("spring"))
                .findFirst();
    }
}

 

isPresent()

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> spring = springClasses.stream()
                .filter(oc -> oc.getTitle().startsWith("jpa"))
                .findFirst();
        boolean present = spring.isPresent();
        System.out.println(present);
    }
}

 

false가 나올 것이다.

get()

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("jpa"))
                .findFirst();
        OnlineClass onlineClass = optional.get();
        System.out.println(onlineClass.getTitle());
    }
}

 

이렇게 비어있는 Optional에서 get을 사용하면 런타임 exception이 발생한다. 그래서 위의 ifPresent를 함께 사용해서 예외처리를 할 수있는데 Optional이 주는 다양한 method를 사용하면 문제를 해결할 수 있다. 가급적이면 get을 사용하지 않고 아래 method를 사용하도록 하자.

 

ifPresent (Consumer<? super T> consumer)

Optional이 null이 아니면 Consumer에서 입력받은 동작을 수행하고 null이면 실행되지 않는다.

 

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();
        optional.ifPresent(oc -> System.out.println(oc.getTitle()));
    }
}

OrElse (T other)

만약 ifPresent에서 Optional이 null이라면 OrElse 뒤의 동작을 수행한다.

 

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("JPA"))
                .findFirst();
        OnlineClass onlineClass = optional.orElse(createNewClass());
        System.out.println(onlineClass.getTitle());
    }

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

 

orElse는 functional interface가 아니라 optional이 감싸고 있는 인스턴스를 입력해준다.

 

위의 8번째 line에서 "JPA"에 대해 filter를 해서 Optional에 값이 없다면 의도한대로

 

creating new online class

New class

 

와 같이 출력할 수 있다. 하지만 "SPRING"에 대해 처리한다면, 마찬가지로

 

creating new online class

spring

 

가 출력된다. creating new online class를 출력하고 싶지 않다면 아래와 같이 orElseGet을 사용하면 된다.

 

orElseGet (Supplier<? extends T> other>

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 online class");
        return new OnlineClass(10,"New class", false);
    }
}

 

orElseThrow (Supplier<? extends X> exceptionSupplier)

대안이 없다면 orElseThrow() 라는NoSuchElementException을 발생시키는데,

 

원하는 다른 값이 있으면 다음과 같이 supplier를 제공할 수 있다.

 

orElseThrow(()→ {

return new IllegalArgumentException()

});

 

orElseThrow(IllegalStateException::new);

filter (Predicate<? super T> predicate)

객체가 존재한다는 가정하에 동작하고, null일 경우 작동하지 않는다. 결과는 Optional이다. filter에 해당되면 Optional이 그대로 나오는 것이고 해당되지 않으면 empty인 Optional이 나온다.

map()

결과는 Optional이다.

optional.map(OnlineClass:getID)

 

그런데 map으로 꺼내는 type이 optional인 경우

optional.map(OnlineClass::getProgress)

 

양파껍질 까듯 여러번 꺼내야 사용하고 싶은 값을 얻을 수 있다.

 

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

 

위와 같은 번거로운 과정을 flatMap으로 줄일 수 있다.

flatMap(Function<? super T, Optional<U>> mapper)

리턴되는 타입이 Optional이면 껍질을 까서 반환한다.

 

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

Optional<Progress> progress = optional.flatMap(OnlineClass::getProgress);

 

 

 

'자바 > 백기선 더 자바, java8' 카테고리의 다른 글

[더 자바, java8] Stream  (0) 2022.11.15
[더 자바, java8] 인터페이스의 변화  (0) 2022.11.15
[더 자바, java8] 함수형 인터페이스와 람다  (0) 2022.11.02
    '자바/백기선 더 자바, java8' 카테고리의 다른 글
    • [더 자바, java8] Stream
    • [더 자바, java8] 인터페이스의 변화
    • [더 자바, java8] 함수형 인터페이스와 람다
    nonosa
    nonosa

    티스토리툴바