[Java8] Chapter 4-2. Optional API 실습 (2)

 

✍️ Optional 실습

Optional API isPresent, get, ifPresent, orElse, orElseGet, orElseThrow, Optional filter, Optional map을 사용해 보자.

 

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 core", false));
	springClasses.add(new OnlineClass(3, "rest api development", false));
}
더보기
public class OnlineClass {
	private Integer id;
	private String title;
	private boolean closed;

	public OnlineClass(Integer id, String title, boolean closed) {
		this.id = id;
		this.title = title;
		this.closed = closed;
	}

	public Integer getId() {
		return id;
	}

	public void setId(Integer id) {
		this.id = id;
	}

	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public boolean isClosed() {
		return closed;
	}

	public void setClosed(boolean closed) {
		this.closed = closed;
	}
}

 

🍊 예제 코드

1. isPresent(), Optional에 값이 있는지 없는지 확인

Stream의 종료 오퍼레이션인 findFirst는 Optional을 반환한다.

title이 spring으로 시작하는 인스턴스가 있을 수도 있고 없을 수도 있기에 findFirst가 Optional을 반환하는 것은 매우 타당하다.

Optional<OnlineClass> onlineClass = springClasses.stream()
		.filter(oc -> oc.getTitle().startsWith("spring"))
		.findFirst();
        
boolean present = optional.isPresent();
System.out.println(present);

// true

 

2. get(), Optional에 있는 값 가져오기

get() 메서드는 Optional에 값이 있다면 값을 꺼내온다.

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

// spring boot

 

그러나, 값이 없을 때 get을 호출하면 문제가 된다.

Optional<OnlineClass> optional = springClasses.stream()
		.filter(oc -> oc.getTitle().startsWith("nodejs"))
		.findFirst();
	
OnlineClass onlineClass = optional.get();
System.out.println(onlineClass.getTitle());
Exception in thread "main" java.util.NoSuchElementException: No value present

 

따라서 get을 호출하기 전엔 isPresent로 감싸줘야 한다.

if (optional.isPresent()) {
	OnlineClass onlineClass = optional.get();
	System.out.println(onlineClass.getTitle());
}

 

Optional이 제공하는 다양한 메서드를 통해서 if 없이 값을 얻어올 수 있다. 가급적 get을 사용하지 않고 이후에 소개할 API를 사용하는 것을 추천한다.

 

3. ifPresent(Consumer), Optional에 값이 있는 경우 그 값을 가지고 Consumer에서 작업을 수행
optional.ifPresent(oc -> System.out.println(oc.getTitle()));

// spring boot

 

4. orElse(T), Optional에 값이 있으면 가져오고 없는 경우엔 T를 반환

단순 람다식에서 인스턴스를 가지고 작업하는 게 아닌 API의 반환 값으로 인스턴스를 받고 싶다면 이후에 소개할 orElse 시리즈를 사용하면 된다.

 

title이 nodejs로 시작하는 인스턴스가 없기에 else에 넘겨준 인자가 orElse의 반환값이 된다.

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

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

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

// creating new class
// new Class

 

만약 Optional에 값이 있는 경우라면 어떨까?

title이 spring으로 시작하는 인스턴스가 있음에도 불구하고 createNewClass()가 실행돼 불필요하게 새로운 인스턴스를 생성하고 있다. 이러한 문제는 orElseGet을 사용해서 해결할 수 있다.

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

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

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

// creating new class
// spring boot

 

5. orElseGet(Supplier), Optional에 값이 있으면 가져오고 없는 경우엔 Supplier를 실행

orElse와 다르게 orElseGet은 인자로 Supplier를 받기 때문에 Optional에 값이 있는 경우 Supplier를 실행하지 않는다.

즉, orElseGet은 Optional에 값이 없는 없는 경우만 lazy 하게 인스턴스를 얻어올 수 있다는 장점이 있다.

    ...
    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(4, "new Class", false);
}

// spring boot

 

그렇다면, orElse는 불필요하고 orElseGet이 항상 좋을까? 그렇지 않다.

이미 만들어진 인스턴스, 혹은 immutable한 형태의 인스턴스를 참고할 땐 orElse가 유리하다.

반면, 동적으로 작업을 한 뒤 인스턴스를 만들어야 한다면 orElseGet이 유리하다.

 

6. orElseThrow, Optional에 값이 있으면 가져오고 없다면 에러를 던짐
Optional<OnlineClass> optional = springClasses.stream()
	.filter(oc -> oc.getTitle().startsWith("nodejs"))
	.findFirst();

OnlineClass onlineClass = optional.orElseThrow(IllegalStateException::new);
Exception in thread "main" java.lang.IllegalStateException

 

7. Optional filter(Predicate), Optional에 들어있는 값 걸러내기
Optional<OnlineClass> optional = springClasses.stream()
	.filter(oc -> oc.getTitle().startsWith("spring"))
	.findFirst()
	.filter(oc -> !oc.isClosed());

boolean present = optional.isPresent();

System.out.println(present);

// false

 

8. Optional map(Function), Optional에 들어있는 값 변환하기
Optional<OnlineClass> optional = springClasses.stream()
	.filter(oc -> oc.getTitle().startsWith("spring"))
	.findFirst();


String title = optional.map(oc -> oc.getTitle()).orElse("default");
System.out.println(title);

// spring boot

 

참고, map의 반환 값이 이미 Optional이라 Optional<Optional<T>>의 형태로 된다면 map이 아닌 flatMap을 사용한다.

 

 

 

본 내용은 백기선님의 자바8 강의 내용입니다.

 

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

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

www.inflearn.com