프로그래밍/JAVA&J2EE

자바 8 Optional

모지사바하 2015. 7. 31. 15:57

Null Pointer Exception 이 피곤하신가요? 그렇다면 자바 8 Optional 을 써보세요 .


예를 들어 아래와 같은 클래스가 있다고 가정합시다.


public class Computer {
private Soundcard soundcard;
public Soundcard getSoundcard() { ... }
...
}

public class Soundcard {
private USB usb;
public USB getUSB() { ... }

}

public class USB{
public String getVersion(){ ... }



우리는 USB 클래스의 getVersion 을 호출하고 싶습니다.


그렇다면 이러한 방법을 생각해볼 수 있습니다.


String version = computer.getSoundcard().getUSB().getVersion(); 


이 코드는 보기는 좋으나 SoundCard 나 USB 가 Null이라면 곧바로 NullPointerException 이 발생합니다.



그래서 보통 NullPointerException 을 방지하기 위해 아래와 같이 코딩을 하죠


String version = "UNKNOWN";
if(computer != null){
Soundcard soundcard = computer.getSoundcard();
if(soundcard != null){
USB usb = soundcard.getUSB();
if(usb != null){
version = usb.getVersion();
}
}
}


이 코드는 NullPointerException 은 방어가 되지만 코드가 길어지고 보기가 별로 좋지 않습니다.



자바 8 의 Optional<T> 는 하스켈 과 스칼라에서 영감을 받았답니다.



예제로 사용했던 클래스를 Optional 을 사용한 버전으로 바꿔보겠습니다.


public class Computer {
private Optional<Soundcard> soundcard;
public Optional<Soundcard> getSoundcard() { ... }
...
}

public class Soundcard {
private Optional<USB> usb;
public Optional<USB> getUSB() { ... }

}

public class USB{
public String getVersion(){ ... }
}


Optional 을 사용하여 일반적인 Null 체크 패턴으로 코드를 작성하는 법을 살펴보겠습니다.

String name = computer.flatMap(Computer::getSoundcard)
.flatMap(Soundcard::getUSB)
.map(USB::getVersion)
.orElse("UNKNOWN");

노트 : 이 코드와 관련하여 자바 8 람다 와 자바 8 스트림 데이터 처리 를 참조하세요.





비어있는 Optional 을 생성하는 방법은 다음과 같습니다.

Optional<Soundcard> sc = Optional.empty();



Null 을 허용하지 않는 Optional 을 생성하는 방법은 다음과 같습니다.

SoundCard soundcard = new Soundcard();
Optional<Soundcard> sc = Optional.of(soundcard);

만약 soundcard 가 Null 이라면 sc 에서 soundcard 를 쓰려고 한다면 NullPointerException 이 발생할 것 입니다.




soundcard 가 null 이 아닐 때 특정 동작을 수행하고 싶을 때 일반적으로 아래와 같이 합니다.

SoundCard soundcard = ...;
if(soundcard != null){
System.out.println(soundcard);
}


Optional 을 사용한다면 ifPresent 메소드를 이용하여 간결하게 처리할 수 있습니다.

Optional<Soundcard> soundcard = ...;
soundcard.ifPresent(System.out::println);

만약 Optional 객체가 비어있다면 아무 동작도 하지 않을 것입니다.





삼항연산자를 이용하여 객체가 Null 일 때 Default Value 를 설정할 수 있습니다.

Soundcard soundcard = 
maybeSoundcard != null ? maybeSoundcard : new Soundcard("basic_sound_card");

Optional 을 사용하면 orElse를 이용하여 간단하게 처리할 수 있습니다.

Soundcard soundcard = maybeSoundcard.orElse(new Soundcard("basic_sound_card"));


조건에 따라 특정행위를 수행하고자 할 때 일반적으로 아래와 같은 방법으로 처리합니ㅏㄷ.

USB usb = ...;
if(usb != null && "3.0".equals(usb.getVersion())){
System.out.println("ok");
}

Optinoal 을 사용하면 filter 메소드를 이용하여 좀 더 간결하게 처리할 수 있습니다.

Optional<USB> maybeUSB = ...;
maybeUSB.filter(usb -> "3.0".equals(usb.getVersion())
.ifPresent(() -> System.out.println("ok"));

Null 을 체크하고 값을 추출하기 위해 Optional 의 map 메소드를 이용할 수도 있습니다.

Optional<USB> usb = maybeSoundcard.map(Soundcard::getUSB);







이러한 코드를

if(soundcard != null){
USB usb = soundcard.getUSB();
if(usb != null && "3.0".equals(usb.getVersion()){
System.out.println("ok");
}
}


map 과 위에서 예시한 filter 를 결합하여 아래와 같이 간결하게 바꿀 수 있습니ㅏㄷ.

maybeSoundcard.map(Soundcard::getUSB)
.filter(usb -> "3.0".equals(usb.getVersion())
.ifPresent(() -> System.out.println("ok"));





위 내용은 많은 자바 개발자분들께 도움이 되기를 바라는 마음에 오라클의 자바 8 Optional 기술 문서를 아주 간략하게 번역한 것입니다. (내가 번역하고 싶은 부분만 0_0;)


약간의 오역이 있을 수도 있습니다.


특정부분은 약간 변형해서 해석한 부분도 있고요. 


하단의 Cascading Optional Objects Using the flatMap Method 단락은 시간관계상 번역하지 않았습니다.


보다 정확하게 보시고 싶으신 분은 http://www.oracle.com/technetwork/articles/java/java8-optional-2175753.html 에서 확인하시면 됩니다.




NullPointerException 은 거의 제일 많이 발생하는 예외이고 경우에 따라선 찾기도 힘이 듭니다.


자바 8의 Optional 을 이용하면 NullPointerException 을 획기적으로 줄일 수 있으며 코드도 많이 간결해질 것 입니다.