Optional 소개 (더 자바, Java 8 강의)
1) Optional이란?
- Optional은 자바 8에 새로 추가된 인터페이스입니다.
Optional은 비어 있을 수도 있고, 값 하나만을 담고 있을 수도 있는 컨테이너 인스턴스의 타입입니다.
예시 코드를 통해 살펴보겠습니다.
public class OnlineClass{
private Integer id;
private String title;
private boolean closed;
public Progress progress;
public OnlineClass(Integer id, String title, booelean closed){
this.id = id;
this.title = title;
this.closed = closed;
}
public Progress getProgress(){
return progress;
}
}
import java.time.duration;
public class Progress{
private Duration studyDuration;
private boolean finished;
public Duration getStudyDuration(){
return studyDuration;
}
}
public class App{
public static void main(String[] args){
OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);
Duration studyDuration = spring_boot.getProgress().getStudyDuration();
System.out.println(studyDuration);
}
}
// 실행 결과
NullPointerException
- OnlineClass 객체인 spring_boot의 Progress 객체가 null이기 때문에 NullPointerException이 발생합니다.
이를 방지하기 위해서 보통 이런 식으로 코딩을 해왔습니다.
public class App{
public static void main(String[] args){
OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);
Progress progress = spring_boot.getProgress();
if(progress != null){
System.out.println(progress.getStudyDuration());
}
}
}
- 이런 식으로 코딩을 할 때의 문제점은 에러를 만들기 좋다는 점입니다.
왜냐하면 널체크를 깜박할 수 있기 때문입니다.
또한 Null을 리턴하는 것 자체도 문제입니다.
- Java 8 이전에는 Null인 경우에 에러를 던지는 방법이 있었습니다.
에러를 던지는 옵션의 문제는
(1) RuntimeException을 던지는 것은 문제가 되지 않지만,
CheckedException을 던지면 에러 처리가 강제되고
(2) 에러가 발생되면 자바는 StackTrace를 찍는데, 이 자체로 리소스를 쓰게 됩니다.
- 예외는 진짜로 필요한 경우에만 쓰는 것이 좋고, 로직을 처리할 때 쓰는 것은 좋지 않습니다.
public class OnlineClass{
public Progress getProgress(){
if(this.progress == null){
throw new IllegalStateException();
}
return progress;
}
}
- 그냥 Null을 리턴할 수도 있는데, 클라이언트 코드가 알아서 널체크를 하는 방법이 있습니다.
public class OnlineClass{
public Progress getProgress(){
return progress;
}
}
public class App{
public static void main(String[] args){
OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);
Progress progress = spring_boot.getProgress();
if(progress != null){
System.out.println(progress.getStudyDuration());
}
}
}
- 자바 8부터는 이것을 좀 더 명시적으로 표현할 수 있는 방법이 생겼습니다.
자바 8부터는 Null이 전달될 수 있는 경우에 Optional로 감싸서 리턴할 수 있습니다.
리턴 타입에만 쓰는 것으로 생각하면 좋습니다.
Optional은 여러 군데(ex) 파라미터)에 쓸 수 있지만,
리턴 타입으로 쓰는 것만이 권장 사항입니다.
public class OnlineClass{
public Optional<Progress> getProgress(){
return Optional.ofNullable(progress);
}
}
- Optional을 사용하면 일종의 박스를 만들어서 객체를 담아 놓습니다.
이 객체는 Null일 수도 있고, 값을 가질 수도 있습니다.
ofNullable 메소드는 객체가 Null일 수도 있는 경우에 쓰이고,
of 메소드는 객체가 Null이 아닌 경우에 사용합니다.
- Optional은 메소드 매개변수 타입으로 쓸 수 있지만,
리턴 타입으로 쓰는 것만이 권장됩니다.
메소드 매개변수 타입으로 쓰면 다음과 같이 코드를 작성할 수 있습니다.
public class OnlineClass{
public void setProgress(Optional<Progress> progress){
progress.ifPresent((p) -> {this.progress = p;});
}
}
- 위와 같이 코드를 작성하면 위험합니다.
왜냐하면 메소드를 호출할 때, Null을 호출할 수 있기 때문입니다.
메소드의 매개변수로 Null이 전달되면 NullPointerException이 발생할 수 있습니다.
public class App{
public static void main(String[] args){
OnlineClass spring_boot = new OnlineClass(1, "spring boot", true);
spring_boot.setProgress(null);
}
}
- 따라서 오히려 널체크를 한 번 더 해야 하는 상황이 발생합니다.
public class OnlineClass{
public void setProgress(Optional<Progress> progress){
if(progress.isPresent()){
progress.ifPresent(p -> this.progress = p);
}
}
}
- 또한, Map의 Key 타입을 Optional로 쓰는 것도 안 좋은 사용법입니다.
그것은 Map 인터페이스의 특징을 깨뜨리는 것입니다.
Map 인터페이스의 가장 큰 특징 중 하나가 Key가 Null을 가질 수 없다는 것입니다.
- 클래스의 필드 타입을 선언할 때, Optional을 쓰는 것도 좋지 않습니다.
이것은 도메인 클래스의 설계와 관련된 문제입니다.
차라리 상하위 클래스를 쪼개거나, Delegation을 사용하는 것이 좋습니다.
public class OnlineClass{
private Integer id;
private String title;
private boolean closed;
public Optional<Progress> progress;
public OnlineClass(Integer id, String title, booelean closed){
this.id = id;
this.title = title;
this.closed = closed;
}
public Progress getProgress(){
return progress;
}
}
- 프리미티브 타입용 Optional은 따로 있습니다. (ex) OptionalInt, OptionaLong 등)
Optional에 프리미티브 타입을 쓸 수 있지만, 박싱, 언박싱이 발생할 수 있으므로 권장되지 않습니다.
박싱, 언박싱이 발생하면 성능에 좋지 않습니다.
// 박싱 & 언박싱 발생
Optional.of(10); // x
OptionalInt.of(10); // o
- Optional을 쓰는 메소드에서 Null을 리턴하지 않는 것도 필요합니다.
왜냐면 이는 Optional을 쓰는 의미를 퇴색시키기 때문입니다.
public class OnlineClass{
public Optional<Progress> getProgress(){
return null;
}
}
- Null을 리턴하고 싶으면, Optional.empty()를 리턴하시면 됩니다.
public class OnlineClass{
public Optional<Progress> getProgress(){
return Optional.empty();
}
}
- Collection, Map, Stream, Array, Optional은 Optional로 또 다시 감싸지 않는 것이 좋습니다.
왜냐하면 Optional을 사용하지 않고도 비어 있다는 것을 표현할 수 있는 타입들이기 때문입니다.
참고
백기선 더 자바, Java 8