
스트림을 공부하며 Optional의 존재를 알게되었고, 최근에 스트림 없이 Optional을 활용하는 코드를 보고, Optional을 단독으로 활용할 수 있구나! 를 깨닫게 되어 공부해보았다.
자바 토이프로젝트를 할 때 일일이 if !=null을 사용하여 코드를 방어했었다. 이렇게 null 체크를 하게되면 코드가 지저분하고 길어지며 가독성이 매우 떨어진다.
이를 Optional을 사용하면 null체크를 직접 하지 않아도 되면서, 명시적으로 해당 변수가 null일 수도 있다는 가능성을 표현할 수 있다.
Optional이란?
Optional은 "존재할 수도 있지만 안 할 수도 있는 객체", 즉 "null이 될 수도 있는 객체"를 감싸고 있는 일종의 래퍼 클래스이다.
Optional 객체 생성
Optional.empty()
null을 담고 있는, 한 마디로 비어있는 Optional 객체를 얻어온다.
Optional<Member> maybeMember = Optional.empty();
Optional.of(value)
null이 아닌 객체를 담고 있는 Optional 객체를 생성한다. null이 넘어올 경우, NPE를 던지기 때문에 주의해서 사용해야 한다.
Optional<Member> maybeMember = Optional.of(aMember);
Optional.ofNullable(value)
null인지 아닌지 확신할 수 없는 객체를 담고 있는 Optional 객체를 생성한다. null이 넘어올 경우, NPE를 던지지 않고 Optional.empty()와 동일하게 비어 있는 Optional 객체를 얻어온다. 해당 객체가 null인지 아닌지 자신이 없는 상황에서사용한다.
Optional<Member> maybeMember = Optional.ofNullable(aMember);
Optional<Member> maybeNotMember = Optional.ofNullable(null);
Optional이 감싸고 있는 객체 접근
4가지 방법이 있다. 모두 Optional 안의 객체가 존재할 경우 동일하게 그 값을 반환한다.
그러나 객체가 존재하지 않은 경우(null일 경우) 다르게 작동한다.
1) get()
비어있는 Optional 객체에 대해서, NoSuchElementException을 던진다.
2) orElse(T other)
비어있는 Optional 객체에 대해서, 넘어온 인자를 반환한다.
3) orElseGet(Supplier<? extends T> other)
비어있는 Optional객체에 대해서, 넘어온 함수형 인자를 통해 생성된 객체를 반환한다. 비어있는 경우에만 함수가 호출되기 때문에 orElse(T other) 대비 성능상 이점을 기대할 수 있다.
4) orElseThrow(Supplier<? extends X> exceptionSupplier)
비어있는 Optional 객체에 대해서, 넘어온 함수형 인자를 통해 생성된 예외를 던진다.
orElse 와 orElseGet
나는 여기서 2, 3번이 잘 와닿지 않았다.
- orElse 메서드는 해당 값이 null이거나 null이 아니어도 실행된다.
- orElseGet메서드는 해당 값이 null일때만 실행된다.
위와 같이 정리할 수 있다.
그러나 내가 직접 여러가지 코드를 작성하며 실험해본 결과, Optional 객체가 비어있지 않고, orElse()에 대체 메서드가 들어갔을 때는 Optional 객체안의 객체가 반환되긴 하지만 대체 메서드도 실행된다.
그런데 Optional 객체가 비어있지 않고, orElse()에 대체 값이 들어갔을때는 그 값이 나오는게 아니라 Optional안의 객체가 나온다.
그러면 orElse 메서드가 null이아니어도 실행되는 거라고 할 수있나..? 하는 생각이 들었다. 값을 넣었을 때와 메서드를 넣었을때의 동작 차이가 궁금했다.
그래서 더 자세히 찾아보았다.
다음은 Optional.java 클래스에 정의되어있는 orElse 메서드의 실제 코드이다.
public T orElse(T other) {
return value != null ? value : other;
}
orElse 메서드의 파라미터에 실제 값을 받았다면, 아래와 같이 동작할 것이다.
public T orElse("default") {
return value != null ? value : "default";
}
그러나 orElse 메서드의 파라미터에 메서드를 받았다면, 아래와 같이 동작할 것이다.
public T orElse(default()) {
return value != null ? value : default();
}
orElse입장에서 default 메서드는 그 자체가 값이 아니고 값을 return해주는 메서드이다. 그래서 default()를 실행시켜 return한 값을 T other로 지정해야되기 때문에 default메서드를 실행시켜야할 필요가 있는 것이다.
그래서 orElse메서드에서 파라미터로 받은 메서드는 Optional안의 값이 null이 아닐때도 실행되는 것이다.
다음 orElseGet 메서드를 살펴보자
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
메서드가 실행되는 시점이 value가 null일때이기 때문이다. get()호출 시점을 보면 된다.
value가 null일때 supplier.get()이 수행되며 메서드가 실행되기 때문에 orElseGet메서드는 null일때만 실행되는 것이다.
Optional의 사용
optional도 stream처럼 사용가능하다. stream이 가지고 있는 map(), filter()등의 메서드를 Optional도 가지고 있기 때문이다.
case 1
null인지, null이 아닌지 확신하기 어려운 값의 length를 구하고 싶을 때
Optional 사용하지 않고 길이 구하기
String text = "test";
int length;
if (text != null) {
length = text.length();
} else {
length = 0;
}
System.out.println("Optional 사용안함" + length);
Optional 사용해서 길이 구하기
int length2 = Optional.ofNullable("test").map(String::length).orElseGet(() -> 0);
System.out.println("Optional 사용함" + length2);
case 2
주문을 한 회원이 살고있는 도시를 반환하는 경우 (객체 안의 객체)
Optional 사용 X
public String getCityOfMemberFromOrder(Order order) {
Optional<Order> maybeOrder = Optional.ofNullable(order);
if (maybeOrder.isPresent()) {
Optional<Member> maybeMember = Optional.ofNullable(maybeOrder.get());
if (maybeMember.isPresent()) {
Optional<Address> maybeAddress = Optional.ofNullable(maybeMember.get());
if (maybeAddress.isPresent()) {
Address address = maybeAddress.get();
Optinal<String> maybeCity = Optional.ofNullable(address.getCity());
if (maybeCity.isPresent()) {
return maybeCity.get();
}
}
}
}
return "Seoul";
}
Optional 사용(map)
public String getCityOfMemberFromOrder(Order order) {
return Optional.ofNullable(order)
.map(Order::getMember)
.map(Member::getAddress)
.map(Address::getCity)
.orElse("Seoul");
}
case 3
조건에 맞는 경우에만 이름 리턴
Optional 사용 X
public Member getMemberIfOrderWithin(Order order, int min) {
if (order != null && order.getDate().getTime() > System.currentTimeMillis() - min * 1000) {
return order.getMember();
}
}
Optional 사용 (filter)
public Optional<Member> getMemberIfOrderWithin(Order order, int min){
return Optional.ofNullable(order)
.filter(o -> o.getData().getTime() > System.currentTimeMillis() - min*1000)
.map(Order::getMember);
}
IfPresent() 메서드
ifPresent(Consumer<? super T> consumer): 이 메소드는 특정 결과를 반환하는 대신에 Optional 객체가 감싸고 있는 값이 존재할 경우에만 실행될 로직을 함수형 인자로 넘길 수 있습니다.
List<String> cities2 = Arrays.asList("seoul","sydney","bangkok");
maybeCity.ifPresent(city -> {
System.out.println("ifpresent 사용 "+ city.length());
});
isPresent() 와 혼동할 수 있다.
isPresent() 메서드
- Boolean 타입
- Optional 객체가 값을 가지고 있다면 true, 값이 없다면 false 리턴
참고자료
'Java' 카테고리의 다른 글
| [ Java ] 정적 팩토리 메서드 (Static factory method) (0) | 2022.12.22 |
|---|---|
| [ Java ] Checked Exception, Unchecked Exception (0) | 2022.12.04 |
| [Java] 함수형 인터페이스 (0) | 2022.12.02 |
| [ Java ] Enum (0) | 2022.11.23 |
| [Java] Arrays.asList 와 List.of (0) | 2022.11.17 |