[더 자바, Java8] 4. Optional

eunsol Jo·2021년 10월 27일
1

🎱  더 자바, Java8

목록 보기
4/5
post-thumbnail

출처 ‣ 더 자바 Java 8, 백기선 / 인프런

4. Optional

Optional = 비어있을 수도 있고, 어떠한 값 하나만 담고 있을수도 있는 인스턴스의 타입
→ Optional이 객체를 감싸는 구조이다.

4.1 등장배경 : 참조형 멤버변수 와 NPE

/* OnlineClass.java */
...
  public Progress progress;

	public Progress getProgress() {
    return progress;
  }

	public void setProgress(Progress progress) {
  	this.progress = progress;
  }
...


/* OptionalTestApp.java */
// 이슈상황 → 참조형 멤버변수 사용시 초기화 되지 않아 null값을 참조 할수 있다.
OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);
Duration studyDuration = spring_boot.getProgress().getStudyDuration();// NullPointExecption 발생
System.out.println(studyDuration);

4.1.1 자바8이전 해결방법

// 방법1. 사전에 null을 체크 → 에러에 대한 스택트레이스를 뽑는것 또한 리소스 낭비이다.
public Progress getProgress() {
    if (progress == null) {
				throw new IllegalStateException();
    }
  	return progress;
}

// 방법2. 클라이언트 코드에서 null을 확인 → 이는 클라이언트에서 노친다면 에러가 발생할 수 있다.
Progress progress = spring_boot.getProgress();
if (progress != null) {
	  System.out.println(progress.getStudyDuration());
}

4.2 Optional 사용 주의사항

[1] Optional은 리턴타입 에만 사용하는것을 권장

/* OnlineClass.java */
...
  // getter의 리턴타입을 optional로변경  *Optional을 리턴타입에만 사용함을 권장!
  public Optional<Progress> getProgress() {
  	return Optional.ofNullable(progress);
	}
...

① ofNullable : null이 들어와도 에러X

② of : null이 들어오면 NullpointExecption

[2] 메소드 매개변수 타입 & 맵의 키 타입 & 인스턴스필드 타입 으로 사용X

1) 메소드 매개변수 타입

// setter에 사용시
public void setProgress(Optional<Progress> progress) {
  	progress.ifPresent(p -> this.progress = p);
}

// 이와 같이 사용한다면 결국, null.ifPresent(...) → NullPointExecption이 발생. Optional사용의 의미가 없다.
객체.setProgress(null);

2) 맵의 키타입

  • Map 인터페이스의 중요 특징 = key는 null일수가 없다.

  • 근데 Optional으로 key가 비어있을 수 있다는건 말이 안된다.

3) 인스턴스필드 타입

  • 필드가 있을수도 있고 없을수도 있다
  • 이는 도메인 클래스의 설계문제
  • 차라리 상위/하위 클래스로 쪼개거나 delegation 사용을 권장

[3] primitiveType의 Optional을 적절히 사용하는것을 권장

Optional.of(10); // 내부에서 boxing, unboxing이 이루어진다. → 성능에 좋지 않다.

OptionalInt.of(10); // 각 primitive타입에 맞는 클래스를 제공하므로 이를 사용하는것 권장

[4] Optional 리턴 메소드에서 null리턴 X

public Optional<Progress> getProgress() {
  	return null; 
}
  • Optional.empty(); 로 리턴해줘야 한다.
  • null을 리턴하면 클라이언트코드에서 "객체.getProgress().ifPresent()"와 같이 사용시 NPE발생 위험!

[5] Collection, Map, Stream Array, Optional 은 Optional로 감싸지 말것

  • 컨테이너 성격의 인스턴스들을 이미 비어있다는것을 표현할 수 있다.

  • 그러므로 Optional로 감싸면 두번 감싸는 형태가 되는것이다. → 무의미

4.2 Optional API

1) Optional 만들기

  • Optional.of()
  • Optional.ofNullable()
  • Optional.empty()

2) Optional 값 여부 확인

  • isPresent()
  • isEmpty() Java11부터 제공

3) Optional 값 가져오기

  • get() : 가급적 사용을 하지 않는것을 권장. null일경우 NoSuchElementException 발생

  • ifPresent(Consumer) : 값이 있는경우 값을 가지고 ~를 하라

  • orElse(T) : 값이 있으면 가져오고 없는 경우에 ~를 리턴하라 *T는 인스턴스 타입

  • orElseGet(Supplier) : 값이 있으면 가져오고 없는 경우에 ~를 하라

  • orElseThrow() : 값이 있으면 가져오고 없는 경우 에러를 던져라

4) Optional 필터/변환

  • Optional filter(Predicate)
  • Optional map(Function)
  • Optional flatMap(Function) : Optional 안의 인스턴스가 Optional인 경우 사용

예제

package java8.optionaltest;

import java8.domain.OnlineClass;
import java8.domain.Progress;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

public class OptionalTestApp {
    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 optional", true));

        // optional을 리턴하는 stream의 메서드는 종료형 operation이라 할수잇다.
        Optional<OnlineClass> optional = springClasses.stream()
                .filter(oc -> oc.getTitle()
                .startsWith("optional"))
                .findFirst();

        boolean present = optional.isPresent();
        boolean empty = optional.isEmpty(); // Java11 부터 제공
        System.out.println(present); // false
        System.out.println(empty); // true;

        /**
         * optional 내부값 가져오기
         * */

        // 1. get()
        //OnlineClass onlineClass = optional.get(); // NoSuchElementException 발생

        // 2. isPresent() + get() = 먼저 확인후 꺼낸다 -> 번거롭다
        /*if (optional.isPresent()) {
            OnlineClass onlineClass = optional.get();
            System.out.println(onlineClass.getTitle());
        }*/

        // 3. ifPresent(Consumer) = 값이 있는 경우만 함수가 동작한다!
        optional.ifPresent(oc -> System.out.println(oc.getTitle()));

        // 4. orElse() = 값이 없는 경우 리턴할 객체를 넣어줌 (이는 기존 Optional이 감싸고 있던 인스턴스 타입이다.)
        // BUT, 이경우 값이 있던 없던 createNewClass()가 실행은 됨. (리턴은 있는경우 그게 리턴되나 createNewClass 함수는 실행이됨)
        // 이미 만들어진 인스턴스를 사용한다면 orElse 함수로 만들어 줘야한다면, orElseGet권장!
        OnlineClass onlineClass = optional.orElse(createNewClass());
        System.out.println(onlineClass.getTitle());

        // 5. orElseGet(Supplier) = 값이 없는 경우만 createNewClass 실행
        OnlineClass onlineClass1 = optional.orElseGet(OptionalTestApp::createNewClass);
        System.out.println(onlineClass1.getTitle());

        // 6. orElseThrow(Supplier)
        OnlineClass onlineClass2 = optional.orElseThrow(() -> {
            return new IllegalArgumentException();
        });
        // 메소드 레퍼런스 사용
        OnlineClass onlineClass3 = optional.orElseThrow(IllegalArgumentException::new);

        // 7. Optional filter(Predicate) = Optional타입이 리턴됨
        Optional<OnlineClass> onlineClass4 = optional.filter(oc -> oc.getId() > 10);

        // 8. Optional map(Function) = map으로 변환한 타입을 Optional로 감싸서 리턴함
        Optional<Integer> integer = optional.map(OnlineClass::getId);
        // Optional을 리턴하는 경우 굉장히 복잡해짐
        Optional<Optional<Progress>> progress = optional.map(OnlineClass::getProgress);
        Optional<Progress> progress1 = progress.orElse(Optional.empty());

        // 9. Optional flatMap(Function)
        Optional<Progress> progress2 = optional.flatMap(OnlineClass::getProgress);
        
    }

    private static OnlineClass createNewClass() {
        System.out.println("createNewClass 함수 실행");
        return new OnlineClass(10 ,"New class", false);
    }
}

https://github.com/eunsolJo/algorithm-study/commit/972922871567e3d8c7471b2040af692c03adec63

profile
Later never comes 👩🏻‍💻

0개의 댓글