✍️ 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을 사용한다.
'Java > Java 8' 카테고리의 다른 글
[Java8] Chapter 5-2. Date와 Time 실습 (2) (0) | 2022.02.23 |
---|---|
[Java8] Chapter 5-1. Date와 Time (1) (0) | 2022.02.22 |
[Java8] Chapter 4-1. Optional (1) (0) | 2022.02.20 |
[Java8] Chapter 3-2. Stream API 실습 (2) (0) | 2022.02.20 |
[Java8] Chapter 3-1. Stream API (1) (0) | 2022.02.18 |