Optional 연습하기

Bruce Han·2023년 2월 11일
0

Java8-정리

목록 보기
10/20
post-thumbnail

이 포스팅의 코드 및 정보들은 강의를 들으며 정리한 내용을 토대로 작성한 것입니다.

/*
* 실습에 앞서 사용할 소스 코드
*/
public static void main(String[] args) {
    List<SchoolClass> programmingClass = new ArrayList<>();
    // Stream이 아닌 다른 종류형을 받을 수 있으면 종료형 operation이다
    programmingClass.add(new SchoolClass(1, "JSP programming", true));
    programmingClass.add(new SchoolClass(2, "C programming", true));
    programmingClass.add(new SchoolClass(3, "Java programming", false));
    programmingClass.add(new SchoolClass(4, "Android programming", false));
    programmingClass.add(new SchoolClass(5, "JavaScript programming", false));
}

Optional에~

값이 있는지 없는지 확인하기

isPresent()

Stream API를 사용할 때도 Optional을 반환하는 operation들이 몇 있다.
Optional을 반환한다는 것은 해당 operation들은 terminal operation이라는 것이다.

과목 이름이 Java로 시작하는 것들 중, 처음으로 나오는 것을 반환하는 타입이 Optional로 나오게 된다.
왜냐하면 "Java"로 시작하는 element가 있을 수도 있고 없을 수도 있기 때문이다.

isPresent()로 있는지 없는지 검사할 수 있다.

isEmpty()

Java 11부터 추가된 isEmpty()로도 값이 있는지 없는지 검사할 수 있다.

있는 값 가져오기

get()

get()을 하면 Optional이 감싸고 있는 type의 인스턴스를 가져온다.

값이 있으면 이상없지만, 값이 없을 때가 문제가 될 수 있다.

비어있는 값을 꺼내오려고 한다면?

비어있는 것으로부터 뭔가를 꺼내오려고 한다면 RuntimeException인 NoSuchElementException이 발생한다.
값을 꺼내기 전에 있는지 없는지 확인하고 값을 꺼내야 하는데 그냥 꺼내려고 하니, 비어있는 것을 꺼낼 때는 예외가 발생하는 것이다.

Optional<SchoolClass> optional = programmingClass.stream()
                .filter(pc -> pc.getSubject().startsWith("Python"))
                .findFirst();
if(optional.isPresent()) {
    SchoolClass schoolClass = optional.get();
    System.out.println(schoolClass.getSubject());
}

if문으로 내용이 들어있는지 체크할 수 있지만, 굳이 get을 써서 if문으로 체킹하지 않아도 Optional에서 쓸 수 있는 메서드는 많다.

값이 있는 경우에 그 값을 가지고 진행하기

ifPresent(Consumer)

optional.ifPresent(sc -> System.out.println(sc.getSubject()));

위에서 if문으로 값의 존재 여부를 체크했다면, 여기에서는 ifPresent()로 한 줄로 값이 있는지 확인하고 그에 따른 코드를 실행할 수 있다.

있는 값을 꺼내올 때는 정상적으로 과목의 이름을 출력하는 결과가 나타난다.

값이 있으면 가져오고 없는 경우에 ~를 리턴하자

만약 ifPresent()로 많은 작업을 해야하고, 뒤에서도 참조를 해야 한다, 무조건 SchoolClass 타입으로 받아야 한다면 or로 시작하는 함수형 인터페이스를 활용할 수 있다.

orElse(T)

위의 사진처럼 orElse()를 활용하여 과목이 있으면 꺼내오고, 없으면 createNewClass()를 통해 새 과목을 만들어서 리턴할 수 있다.

orElse안에 들어가는 파라미터는 functional interface가 들어가는 것이 아니라, 인스턴스가 들어오는 것이다.
Optional이 감싸고 있는 타입의 인스턴스를 넘겨주는 것이다.

functional interface를 구현하는 lambda expression이나 method reference를 주는 게 아니다.

그런데 이미 존재하는 데이터를 가지고 와도 creating new school class라는 문구가 나온다.
createNewClass()에 대한 연산은 무조건 하도록 되어 있기 때문이다.

값이 있으면 가져오고 없는 경우에 ~를 하자

orElseGet(Supplier)

orElseGet의 파라미터 타입으로 Supplier를 줘야 하는데

SchoolClass schoolClass = optional.orElseGet(() -> createNewClass());

이렇게 lambda expression으로 해도 되고

SchoolClass schoolClass = optional.orElseGet(Main::createNewClass);

method reference으로 파라미터를 넣어도 된다.

orElseGet()을 사용하면 orElse()와는 다르게, 이미 있는 데이터는 파라미터에 있는 메서드를 실행시키지 않고 과목의 이름만 출력한다.
Main::createNewclass는 함수형 인터페이스의 구현체이기 때문에 추후에 lazy하게 다룰 수 있다.

물론 없는 데이터가 올 때는 createNewClass()를 호출해서 새 과목이 만들어질 것이다.

이미 만들어져 있는 것(상수 등)을 참고할 때는 orElse()가 적합하다.

동적으로 작업해서 만들어 내야 하거나 추가 작업을 해야 할 경우에는 orElseGet()을 쓰는 게 적합할 것이다.

orElseThrow()

데이터가 없어도 뭔가를 만들어줄 수 없는 경우에 사용한다. 비어있는 데이터가 올 경우에는 에러를 던진다.
기본적으로는 NoSuchElementException 예외를 던지도록 되어있다.

던지길 원하는 에러가 있다면 Supplier로 제공해줄 수 있다.

혹은 다음과 같이 생성자를 참조해서 원하는 에러를 던지게 할 수도 있다.

들어있는 값 걸러내기

값들이 존재하는 가정하에 원하는 값을 걸러내는 옵션인 filter()를 활용할 수 있다.
없는 경우에는 empty인(비어있는) 옵셔널이 나온다.

filter(Predicate)

조건에 해당되지 않는 filter의 결과로 비어있는 Optional 타입이 나오게 된다.
만약에 필터에 해당되면 isEmpty()/isPresent()로 체크했을 때 false가 나온다.

들어있는 값 변환하기

map(Function)

map같은 경우에는 결과가 Optional로 나온다. map이니까 전달해준 함수가 리턴하는 타입에 따라 Optional이 담고 있는 타입이 달라진다.

//optional.map(sc -> sc.getId());
optional.map(SchoolClass::getId);

타입이 Integer인 과목의 id를 가져오려고 하면

Optional로 감싸진 Integer형의 변수가 나오게 되고, Java로 시작하는 이름의 과목의 첫번째 값을 꺼내올 수 있으니 이를 실행하면 true가 나오게 될 것이다.

만약 map으로 꺼내는 타입 자체가 Optional타입일 경우에는 map이 복잡해지는 경우가 있다.

public class SchoolClass {
    private Integer id;

    private String subject;

    private boolean closed;

    public Progress progress;

    public SchoolClass(Integer id, String subject, boolean closed) {
        this.id = id;
        this.subject = subject;
        this.closed = closed;
    }

	...
    
    public Optional<Progress> getProgress() {
        return Optional.empty();
    }
	
    ...
}

progress를 꺼내려고 하는데 getProgress()의 반환 타입이 Optional로 감싸져 있으므로

progress를 마침 값이 없는 상태로 가져왔기 때문에 NoSuchElementException이 발생하지만, 그보다 중요한 건 Optional이 두 번 중첩되어서 꺼낼 때 두 번씩 or로 값의 존재 여부를 체크를 해줘야 한다는 것이다.

flatMap(Function)

map()의 실습에서 나온 getProgress()같이 반환 타입이 Optional로 감싸져 있는 경우에는 Optional이 제공하는 flatMap()을 사용하는 게 유리하다.

매핑해서 꺼내는 타입 자체가 만약 Optional이면 안에서 Optional 껍질을 한 번 까준다.

map()과 flatMap()은 비슷하지는 않지만 그래도 비교하자면 다음과 같다.

flatMap()과 .orElse(Optional.empty())가 오히려 좀 더 비슷하다.

그리고 Optional에서의 flatMap과 Stream에서의 flatMap과는 다르다.

Stream에서 쓰는 map은 1대1 매핑이다. 어떤 element 하나가 오면 다른 result도 하나인데, Stream의 flatMap은 뭔가를 또 담고 있는, 컨테이너 성격의 안에 있는 걸 끄집어 내는 그럴 때 주로 쓰이기 때문에 input은 하나지만 output이 여러 개인 상황이 생길 때 사용한다.(?)

정리

  • Optional 만들기

    • Optional.of()
    • Optional.ofNullable()
    • Optional.empty()
  • Optional에 값의 존재 여부 확인하기

    • isPresent()
    • isEmpty() (Java 11부터 제공)
  • Optional에 있는 값 가져오기

    • get()
    • 만약 비어있는 Optional에서 무언가를 꺼낸다면?
  • Optional에 값이 있는 경우에 그 값을 가지고 ~~를 하자

    • ifPresent(Consumer)
    • 예) Java로 시작하는 이름의 과목이 있으면 그 과목의 id를 출력하자
  • Optional에 값이 있으면 가져오고, 없는 경우에 ~~를 리턴하자

    • orElse(T)
    • 예) Python으로 시작하는 이름의 과목이 없다면 비어있는 과목을 리턴하자
  • Optional에 값이 있으면 가져오고, 없는 경우에 ~~를 하자

    • orElseGet(Supplier)
    • 예) Python으로 시작하는 이름의 과목이 없다면 새로 만들어서 리턴하자
  • Optional에 값이 있으면 가져오고, 없는 경우에는 에러를 던지자

    • orElseThrow()
  • Optional에 들어있는 값 걸러내기

    • Optional filter(Predicate)
  • Optional에 들어있는 값 변환하기

    • Optional map(Function)
    • Optional flatMap(Function) : Optional 안에 들어있는 인스턴스가 Optional인 경우에 사용하면 편리하다

Reference

더 자바, Java 8 - 백기선 강사님 강의를 토대로 정리했습니다.

profile
만 가지 발차기를 한 번씩 연습하는 사람은 두렵지 않다. 내가 두려워 하는 사람은 한 가지 발차기를 만 번씩 연습하는 사람이다. - Bruce Lee

0개의 댓글