1. Optional이란?
개발을 하다 보면 NullPointException(NPE)을 만나게 된다. 가장 많이 발생하는 에러 중 하나라고 하는데, 이를 피하기 위해선 null을 체크하는 로직이 추가되어야 한다.
DeviceVo deviceVo = deviceDao.findById(deviceId); //id에 해당하는 데이터가 없다면 deviceVo는 null이다.
deviceVo.getName(); //deviceVo는 null이기 때문에 NullPointException이 발생한다.
deviceVo.getModel().getModelId(); //deviceVo가 null이 아니더라도 Model이 null이면 NullPointException이 발생한다.
if(deviceVo != null){
deviceVo.getName(); //NullPointException을 방지하기 위해 null이 아닌 경우에만 name을 가져오도록 로직을 짜야한다.
if(deviceVo.getModel != null) {
deviceVo.getModel().getModelId();
}
}
null 체크해야 될 부분이 많다면 코드가 복잡해져 가독성이 떨어지는 단점이 있다.
Java 8에서는 Optional<T> 클래스를 도입하였는데, '존재할 수도 있지만 안 할 수도 있는 객체'로 null이 될 수도 있는 객체를 감싸고 있는 Wrapper 클래스이다.
Optional로 객체를 감싸서 사용하게 되면 NPE 방지를 위해 null 체크를 직접 하지 않아도 되며, 명시적으로 해당 변수가 null일 수도 있다는 가능성을 포함하고 있기때문에 불필요한 방어 로직을 줄일 수 있다.
2. Optional 사용법
- Optional 생성하기
빈 Optional 객체는 아래와 같이 생성할 수 있다. (Optional 객체 자체는 있지만 내부에서 가리키는 참조가 없는 경우를 말함)
Optional<String> optional = Optional.empty();
System.out.println(optional); //결과 : Optional.empty
System.out.println(optional.isPresent()); //결과 : false
value가 null인 경우는 NullPointException 발생하기 때문에 값이 반드시 있는 경우에만 of() 메소드를 사용해야 한다.
Optional<String> optional = Optional.of(value);
ofNullable() 메소드는 value가 null이여도 NullPointException이 발생하지 않고, Optional.empty가 리턴된다.
Optional<String> optional = Optional.ofNullable(value);
System.out.println(optional);//결과 : Optional.empty
- Optional이 제공하는 메소드
- filter()
filter 메소드의 인자인 람다식이 true이면 Optional 객체를 그대로 통과시키고, false이면 Optional.empty()를 리턴하여 추가로 처리가 되지 않도록 한다.
String result1 = Optional.of("ABCDE").filter((val) -> val.contains("ABC")).orElse("Does not contain ABC");
System.out.println(result1); //ABCDE
String result2 = Optional.of("CDEFG").filter((val) -> val.contains("ABC")).orElse("Does not contain ABC");
System.out.println(result2); //Does not contain ABC
- map()
입력받은 값을 다른 값으로 변환하는 메서드이다.
String result3 = Optional.of("abcde").map(String::toUpperCase).orElse("fail");
System.out.println(result3); //ABCDE
- isPresent()
값이 있으면 true를 반환하고 그렇지 않으면 false를 반환한다.
- ifPresent()
람다식을 인자로 받는데, 값이 존재할 때만 람다식이 적용된다. 값이 존재하지 않으면 실행되지 않는다.
Optional.of("ABCDE").ifPresent(System.out :: println); //결과 : ABCDE
Optional.ofNullable(null).ifPresent(System.out :: println); //결과 : 아무것도 출력되지 않음
- get()
Optional 객체가 가지고 있는 value를 가져온다. 만약 객체에 값이 없다면 NoSuchElementException이 발생한다.
- orElse()
Optional 객체가 비어 있다면 orElse() 메소드의 지정된 값으로 리턴된다.
- orElseGet()
Optional 객체가 비어 있다면 기본값으로 제공할 supplier를 지정한다. orElse()의 경우 값이 null이든 아니던 호출 되며, orElseGet()은 null일 때만 호출된다.
- orElseThrow()
연산을 끝낸 후에도 Optional 객체가 비어 있다면 예외 공급자 함수를 통해 예외를 발생시킨다.
String result1 = Optional.of("CDEFG").filter((val) -> val.contains("ABC")).orElseThrow(NoSuchElementException::new);
System.out.println(result1);
//결과
Exception in thread "main" java.util.NoSuchElementException
at java.base/java.util.Optional.orElseThrow(Optional.java:385)
여기까지가 Java 8에서 제공하는 Optioanl 메소드이다.
Java9에선 or(), isPresentOrElse(), stream() 메소드가 추가되었고, Java10에서는 인자를 받지 않는 orElseThrow() 메소드가 추가되었다. 이 부분에 대한 설명은 추후 추가하겠다.
3. 활용 예시
1번에 있는 샘플 소스(Optional이 적용 안된 소스)에 Optional을 도입해 보았다.
Optional<DeviceVo> deviceVo = Optional.ofNullable(deviceDao.findById(deviceId));
Optional<ModelVo> modelVo = deviceVo.map(DeviceVo::getModel);
Optional<String> modelId = modelVo.map(ModelVo::getModelId);
String modelId = modelId.orElse("모델 없음");
그리고 위 코드는 아래와 같이 축약해서 사용 가능하다.
String modelId = Optional.ofNullable(deviceDao.findById(deviceId))
.map(DeviceVo::getModel)
.map(ModelVo::getModelId)
.orElse("모델 없음");
'개발 > JAVA' 카테고리의 다른 글
[JAVA] 대칭키 암호화 알고리즘 키 제한 오류 해결 : Illegal key size (0) | 2022.01.26 |
---|---|
[JAVA] class file for javax.interceptor.InterceptorBinding not found (0) | 2022.01.26 |
[JAVA] Error, Checked Exception, Unchecked Exception 비교 (0) | 2022.01.12 |
[JAVA] 생성자 대신 정적 팩토리 메소드를 고려해야 하는 이유! (0) | 2022.01.12 |
[JAVA] 정적 팩토리 메소드란? (static factory method) (1) | 2022.01.10 |
댓글