
출처 ‣ 더 자바 Java 8, 백기선 / 인프런
 
Optional = 비어있을 수도 있고, 어떠한 값 하나만 담고 있을수도 있는 인스턴스의 타입
→ Optional이 객체를 감싸는 구조이다.
/* 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);
// 방법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());
}
/* OnlineClass.java */
...
  // getter의 리턴타입을 optional로변경  *Optional을 리턴타입에만 사용함을 권장!
  public Optional<Progress> getProgress() {
  	return Optional.ofNullable(progress);
	}
...
// setter에 사용시
public void setProgress(Optional<Progress> progress) {
  	progress.ifPresent(p -> this.progress = p);
}
// 이와 같이 사용한다면 결국, null.ifPresent(...) → NullPointExecption이 발생. Optional사용의 의미가 없다.
객체.setProgress(null);
Map 인터페이스의 중요 특징 = key는 null일수가 없다.
근데 Optional으로 key가 비어있을 수 있다는건 말이 안된다.
Optional.of(10); // 내부에서 boxing, unboxing이 이루어진다. → 성능에 좋지 않다.
OptionalInt.of(10); // 각 primitive타입에 맞는 클래스를 제공하므로 이를 사용하는것 권장
public Optional<Progress> getProgress() {
  	return null; 
}
컨테이너 성격의 인스턴스들을 이미 비어있다는것을 표현할 수 있다.
그러므로 Optional로 감싸면 두번 감싸는 형태가 되는것이다. → 무의미
get() : 가급적 사용을 하지 않는것을 권장. null일경우 NoSuchElementException 발생
ifPresent(Consumer) : 값이 있는경우 값을 가지고 ~를 하라
orElse(T) : 값이 있으면 가져오고 없는 경우에 ~를 리턴하라 *T는 인스턴스 타입
orElseGet(Supplier) : 값이 있으면 가져오고 없는 경우에 ~를 하라
orElseThrow() : 값이 있으면 가져오고 없는 경우 에러를 던져라
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