본 게시물은 인프런 백기선님 강의 "더 자바, java8"을 학습하고 개인적으로 정리한 내용입니다.
https://www.inflearn.com/course/the-java-java8#
목차
1. Optional 소개
2. Optional API
1. Optional 소개
Optional 배경
App.java
public class App {
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 jpa", true));
springClasses.add(new OnlineClass(3,"spring mvc", false));
springClasses.add(new OnlineClass(4,"spring core", false));
springClasses.add(new OnlineClass(5,"rest api development", false));
OnlineClass spring_boot = new OnlineClass(1,"spring boot", true);
Duration studyDuration = spring_boot.getProgress().getStudyDuration();
System.out.println(studyDuration);
}
}
Progress.java
public class Progress {
private Duration studyDuration;
private boolean finished;
public Duration getStudyDuration() {
return studyDuration;
}
public void setStudyDuration(Duration studyDuration) {
this.studyDuration = studyDuration;
}
}
Duration studyDuration = springboot.getProgerss().getStudyDuration(); 에서
nullPointException이 일어난다. progress 기본값이 null이고, 아무것도 할당하지 않았기 때문이다.
그래서 다음과 같이 예외처리를 할 수 있는데
Progress progress = spring_boot.getProgress();
if (progress != null) {
System.out.println(progress.getStudyDuration());
}
위와 같이 예외처리하면 매번 null 체크를 할 수 없기 때문에 사람이라면 실수가 나올 수 밖에 없다.
java8부터는 다음과 같이 null이 전달될 수 있는 경우를 대비해 반환값을 Optional로 감싼다.
Progress progress = spring_boot.getProgress();
if (progress != null) {
System.out.println(progress.getStudyDuration());
}
즉 Optional은 오직 값 한개가 들어있을 수도, 없을 수도 있는 컨테이너이다.
주의할 것
1. return 타입으로만 사용할 것
문법적으로는 제한은 없지만 리턴 타입으로만 사용한다. 메서드의 파라미터에 Optional이 오는 경우를 생각하면, 그 Optional이 null인지 Optional인지 검사해야하는데, 중복으로 null을 검사하게 되므로, Optional의 취지에 어긋난다. map의 key type을 Optional로 쓰는 것 또한 같은 맥락으로 위험하다.
2. 프리미티브 타입용 Optional을 사용하자.
Optional.of(10)의 경우 오토박싱 언박싱이 일어나기 때문에 성능저하가 발생한다.
OptionalInt.of(10)과 같이 프리미티브 타입용 Optional을 사용하자.
3. null을 return하지 말자
Optional의 취지에 어긋난다.
//bad case
public Optional<Progress> getProgress() {
return null;
}
//better case
public Optional<Progress> getProgress() {
return null;
}
4. Collections, Map, Stream Array, Optional을 Optional로 감싸지 말자
위의 type들은 비어있다는 것 자체를 표현할 수 있는 type들이다. 그 자체로 비어있는지 판단할수 있는 container를 Optional로 감싸는 것은 두번 중복해서 감싸는 것과 같다.
Optional API
다음 코드와 같이 findFirst()를 사용하면 Optional을 return한다. 값이 null일 수도 있기 때문이다.
public class App {
public static void main(String[] args) {
List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1,"spring boot", true));
springClasses.add(new OnlineClass(5,"rest api development", false));
Optional<OnlineClass> spring = springClasses.stream()
.filter(oc -> oc.getTitle().startsWith("spring"))
.findFirst();
}
}
isPresent()
public class App {
public static void main(String[] args) {
List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1,"spring boot", true));
springClasses.add(new OnlineClass(5,"rest api development", false));
Optional<OnlineClass> spring = springClasses.stream()
.filter(oc -> oc.getTitle().startsWith("jpa"))
.findFirst();
boolean present = spring.isPresent();
System.out.println(present);
}
}
false가 나올 것이다.
get()
public class App {
public static void main(String[] args) {
List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1,"spring boot", true));
springClasses.add(new OnlineClass(5,"rest api development", false));
Optional<OnlineClass> optional = springClasses.stream()
.filter(oc -> oc.getTitle().startsWith("jpa"))
.findFirst();
OnlineClass onlineClass = optional.get();
System.out.println(onlineClass.getTitle());
}
}
이렇게 비어있는 Optional에서 get을 사용하면 런타임 exception이 발생한다. 그래서 위의 ifPresent를 함께 사용해서 예외처리를 할 수있는데 Optional이 주는 다양한 method를 사용하면 문제를 해결할 수 있다. 가급적이면 get을 사용하지 않고 아래 method를 사용하도록 하자.
ifPresent (Consumer<? super T> consumer)
Optional이 null이 아니면 Consumer에서 입력받은 동작을 수행하고 null이면 실행되지 않는다.
public class App {
public static void main(String[] args) {
List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1,"spring boot", true));
springClasses.add(new OnlineClass(5,"rest api development", false));
Optional<OnlineClass> optional = springClasses.stream()
.filter(oc -> oc.getTitle().startsWith("spring"))
.findFirst();
optional.ifPresent(oc -> System.out.println(oc.getTitle()));
}
}
OrElse (T other)
만약 ifPresent에서 Optional이 null이라면 OrElse 뒤의 동작을 수행한다.
public class App {
public static void main(String[] args) {
List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1,"spring boot", true));
springClasses.add(new OnlineClass(5,"rest api development", false));
Optional<OnlineClass> optional = springClasses.stream()
.filter(oc -> oc.getTitle().startsWith("JPA"))
.findFirst();
OnlineClass onlineClass = optional.orElse(createNewClass());
System.out.println(onlineClass.getTitle());
}
private static OnlineClass createNewClass() {
System.out.println("creating new online class");
return new OnlineClass(10,"New class", false);
}
}
orElse는 functional interface가 아니라 optional이 감싸고 있는 인스턴스를 입력해준다.
위의 8번째 line에서 "JPA"에 대해 filter를 해서 Optional에 값이 없다면 의도한대로
creating new online class
New class
와 같이 출력할 수 있다. 하지만 "SPRING"에 대해 처리한다면, 마찬가지로
creating new online class
spring
가 출력된다. creating new online class를 출력하고 싶지 않다면 아래와 같이 orElseGet을 사용하면 된다.
orElseGet (Supplier<? extends T> other>
public class App {
public static void main(String[] args) {
List<OnlineClass> springClasses = new ArrayList<>();
springClasses.add(new OnlineClass(1,"spring boot", true));
springClasses.add(new OnlineClass(5,"rest api development", false));
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 online class");
return new OnlineClass(10,"New class", false);
}
}
orElseThrow (Supplier<? extends X> exceptionSupplier)
대안이 없다면 orElseThrow() 라는NoSuchElementException을 발생시키는데,
원하는 다른 값이 있으면 다음과 같이 supplier를 제공할 수 있다.
orElseThrow(()→ {
return new IllegalArgumentException()
});
orElseThrow(IllegalStateException::new);
filter (Predicate<? super T> predicate)
객체가 존재한다는 가정하에 동작하고, null일 경우 작동하지 않는다. 결과는 Optional이다. filter에 해당되면 Optional이 그대로 나오는 것이고 해당되지 않으면 empty인 Optional이 나온다.
map()
결과는 Optional이다.
optional.map(OnlineClass:getID)
그런데 map으로 꺼내는 type이 optional인 경우
optional.map(OnlineClass::getProgress)
양파껍질 까듯 여러번 꺼내야 사용하고 싶은 값을 얻을 수 있다.
Optional<Optional<Progress>> progress = optional.map(OnlineClass::getProgress);
Optional<Progress> progress1 = progress.orElseThrow();
progress1.orElseThrow()
위와 같은 번거로운 과정을 flatMap으로 줄일 수 있다.
flatMap(Function<? super T, Optional<U>> mapper)
리턴되는 타입이 Optional이면 껍질을 까서 반환한다.
Optional<Optional<Progress>> progress1 = optional.map(OnlineClass::getProgress);
Optional<Progress> progress2 = progress1.orElse(Optional.empty());
Optional<Progress> progress = optional.flatMap(OnlineClass::getProgress);
'자바 > 백기선 더 자바, java8' 카테고리의 다른 글
[더 자바, java8] Stream (0) | 2022.11.15 |
---|---|
[더 자바, java8] 인터페이스의 변화 (0) | 2022.11.15 |
[더 자바, java8] 함수형 인터페이스와 람다 (0) | 2022.11.02 |