티스토리 뷰
개발을 하다보면 null 값 때문에 이런저런 오류들을 마주한다.
당연히 null 값이 아닐거라고 생각해서 사용하지만 객체의 경우는 기본적으로 nullable 하므로 얼마든지 null 일 수 있다.
만일 아래와 같이 특정 함수에서 list 객체를 반환받고 해당 객체에서 첫 번째 요소를 꺼내서 활용할 때 반환 받은 값이 null일 경우 에러가 발생한다.
이는 함수를 호출하는 곳에서는 null 값이 아닌 객체를 넘겨 줄 것이라고 생각하기 때문이다.
public void test() {
List<String> list = subFunction();
System.out.println(list.get(0));
}
물론 애초에 객체를 반환하는 곳 내부에서 null 값이 아닌 빈 객체를 반환하도록 처리하거나 함수를 호출하는 곳에서 null 값일 수도 있다는 것을 인지하고 null 처리를 하면 문제가 없다. 하지만 만일 함수 내부에서 특정 상황일 때 null 값을 반환할 수밖에 없을 경우 null 값을 반환할 수도 있다는 것을 함수를 호출하는 곳에 알려줄 수 있으면 좋다.
이 때 Optional를 반환해 함수를 호출하는 곳에 null 값일 수 있다는 것을 알려주고 null 값을 Optional을 통해 처리할 수 있다.
Optional<T> 이란
java.util의 Optional<T>는 java 8부터 지원하는 객체를 감싸는 래퍼 클래스(Wrapper class)이다. 객체를 Optional 안에 넣어서 한 번 감싸는데 이 때 객체는 null일 수도 있고 null이 아닐 수도 있다. Optional을 사용함으로써 Optional에 감싸진 객체가 null일 수 있다는 것을 명시해주는 것과 동시에 null 처리를 조건문 코드를 통해 처리하지 않고 Optional을 통해 처리할 수 있다.
Optional로 포장하기
null 처리가 필요한 객체를 Optional로 포장하는 것은 아래와 같다.
Optional<String> srt = Optional.ofNullable("test!!");
Optional.ofNullable()은 null일 수 있는 값을 Optional로 감쌀 때 사용하는 함수로 만일 값이 null일 경우 비어 있는(empty) Optional 객체가 반환된다.
Optional을 포장하는 방법은 Optional.ofNullable()말고 Optional.of()를 사용하는 방법도 있는데 이 때는 값이 무조건 null이 아닌 값이어야 한다. 안 그러면 NullPointException이 발생한다.
Optional<String> str = Optional.of("test!!");
Optional은 null 값을 처리하기 쉽게 하기 위한 래퍼 클래스라고 했다. 그런데 Optional.of()를 이용할 경우 애초에 null 값을 못 넣는데 왜 있는 것일까?
예를 들어 Box는 무조건 nullable하지 않아야 한다고 생각했을 때 Optional.ofNullable()을 사용하면 실수로 null인 Box를 Optional로 감싸도 에러가 발생하지 않아 알아채기 힘들다. 하지만 Optional.of()를 사용하면 null 값을 넣었을 때 NullPointException이 발생하므로 실수로 nullable 하지 않은 Box를 null 값으로 만들었다는 것을 알아차리기 좋다. (이 때는 애초에 Optional의 기능이 필요하거나 꼭 Optional로 만들어야 하지 않는 이상 Optional을 사용해야하는지 다시 생각해보는 것이 좋다.)
Optional로 null 처리하기
Optional로 null 값을 다루는 것을 간단한 예제를 통해 확인해보자.
null이 아닐 경우 특정 동작 하기
위에 있었던 box안의 pouch가 아래와 같다고 하자.
public class Pouch {
int money;
String memo;
}
box가 nullable 하지 않을 때 pouch가 null이 아닐 때 pouch 안의 돈과 메모를 출력하는 것을 Optional로 하면 아래와 같다.
getBox()는 Optional<Box>를 반환한다. (getBox()에서 Box를 반환받고 optionalTest()에서 Optional.ofNullable() 또는 Optional.of()를 통해 Box를 감싸줘도 무방하다.)
public void optionalTest() {
Optional<Box> box = getBox();
box.map(Box::getPouch)
.ifPresent(pouch ->
System.out.println(String.format("pouch money: %d, memo: %s",
pouch.getMoney(), pouch.getMemo()))
);
}
여기서 Optional은 Stream에서 쓸 수 있는 map(), flatMap(), filter() 함수를 제공한다.
따라서 Optional.map()을 통해 box의 pouch를 꺼낼 수 있다.
box.map(Box::getPouch)
https://pamyferret.tistory.com/43
그리고 Optional에는 ifPresent()라는 함수를 제공하는데 이 함수를 통해 해당 값이 존재할 경우의 동작을 정의할 수 있다.
여기서는 map(Box::getPouch)를 통해 꺼낸 pouch와 box를 통해 정보를 출력해줬다. map(Box::getPouch) 결과로 Original<Pouch>가 반환된다.
box.map(Box::getPouch)
.ifPresent(pouch ->
System.out.println(String.format("pouch money: %d, memo: %s",
pouch.getMoney(), pouch.getMemo()))
);
위 코드를 통해 pouch가 null이 아닐 경우 아래와 같은 메시지가 콘솔에 출력된다.
pouch money: 10000, memo: 식빵 사가기
ifPresent()말고 Optional에는 isPresent()도 존재하는데 이는 단순하게 Optional로 감싼 값이 null인지 null이 아닌지 boolean을 반환해주는 함수다.
Optional<Box> box = getBox();
if(!box.isPresent) {
System.out.println("상자가 없습니다!");
}
null일 경우 다른 값 반환하기
이번에는 getBox()로 받은 Optional<Box>에서 box를 꺼내서 사용한다고 가정해보자. 이 때 제일 기본인 Optional.get()을 통해 box를 꺼낼 수 있다.
Optional<Box> optionalBox = getBox();
Box box = optionalBox.get();
하지만 Optional.get()은 만일 안의 값이 없을 경우(null일 경우) NoSuchElementException 에러가 발생한다. box가 null일 경우 아래와 같이 try-catch를 통해서 에러를 잡을 수도 있다.
try {
Optional<Box> optionalBox = getBox();
Box box = optionalBox.get();
...
} catch (NoSuchElementException e) {
System.out.println("상자가 없습니다.");
}
하지만 try-catch로 값이 존재하지 않을 때의 동작을 정의하는 것은 if로 box가 null인지 체크하는 것보다 코드가 더 길어지고 복잡해진다. 이러면 Optional을 사용하느니 그냥 if문을 통해 null 값을 체크하는 것이 낫다.
이럴 때 사용하라고 Optional에서 제공하는 함수들이 있다.
orElse(T other)
Optional에서 값을 꺼내는데 만일 존재하지 않을 경우 매개변수로 정의한 other를 반환한다.
orElse안에 정의해놓은 new Box()는 Optional의 box가 null이든 null이 아니든 실행되고 null일 경우 new Box()로 만들어진 빈 box를 반환한다.
Optional<Box> optionalBox = getBox();
Box box = optionalBox.orElse(new Box());
orElseGet(Supplier<? extends T> other)
orElse와 비슷하지만 매개변수에 Supplier가 들어가는데 아래와 같이 매개변수가 없이 무언가 반환해주는 함수를 넣으면 된다.
orElse(T other)와 달리 매개변수로 넣은 동작은 Optional의 값이 null일 때만 동작한다. 따라서 orElse보다는 orElseGet을 사용하길 추천한다.
Optional<Box> optionalBox = getBox();
Box box = optionalBox.orElseGet(() -> new Box());
orElseThrow(Supplier<? extends X> exceptionSupplier)
orElse, orElseGet과 달리 null 값일 때 별도 지정한 값을 반환하는 대신 에러를 발생시킨다.
Optional<Box> optionalBox = getBox();
Box box = box.orElseThrow(() -> new Exception("상자가 없습니다"));
Optional 사용 시 주의 사항
여기까지 보면 왜 x != null 이런 식으로 조건 처리하는 것이 아직도 있는지 의아할 것이다. Optional을 사용하게 될 경우 Optional이라는 별도 부가적인 객체를 만드는 것이고 그에 따라 비용이 더 발생한다. 또한 null 처리 하는 조건문이 다른 조건문과 섞이지 않아서 복잡하지 않을 경우에는 Optional에게 null 처리를 맡긴 코드보다 조건문에 x != null로 null 처리를 하는게 더 직관적이고 읽기 쉬울 수 있다.
따라서 내가 끌어다 쓰는 라이브러리 함수의 반환 값이 Optional인 경우를 제외하고는 Optional을 꼭 써야하는지 생각을 하고 사용하는 것이 좋다.
또한 orElse(new Object())보다는 orElseGet(() -> new Object())가 좋다는 것도 잊지 말자.
✋ Optional
https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html
✋ Why use Optional.of over Optional.ofNullable
https://stackoverflow.com/questions/31696485/why-use-optional-of-over-optional-ofnullable
'Java' 카테고리의 다른 글
자바의 function interface (0) | 2022.02.04 |
---|---|
직렬화 프록시 패턴이란 (0) | 2022.01.28 |
Kryo serializer (0) | 2022.01.03 |
ThreadLocal이란? (0) | 2021.12.30 |
ConstraintValidator로 컨트롤러에서 받는 값 유효성 체크하기 (0) | 2021.10.14 |
- Total
- Today
- Yesterday
- MAC
- Caching
- 역직렬화
- Intellij
- springboot
- 스프링부트
- enum
- 메시지큐
- DB
- 데이터베이스
- 공간데이터
- 캐시
- eclipse
- PostgreSQL
- rabbitmq
- 캐싱
- Java
- HttpClient
- ssh
- JPA
- DATABASE
- Spring
- annotation
- 이클립스
- 어노테이션
- k8s
- 자바
- mockito
- postgres
- cache
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |