변수에 값이 할당되어 있지 않은 상태를 Null이라고 한다. Null인 상태일때 변수에 접근하게 되면 Java는 NullPointerException이 발생한다. 그래서 우리는 매번 Null Check를 해줘야 한다.
다음은 Null Check를 해주는 User와 Staff가 null이 아닐시 staff를 User 에게 할당하는 예시 코드이다.
User user = null;
Staff staff = null;
...
~코드 내용들~
...
if(user!=null){
if(staff!=null){
user.forEach((u)->{
...기타 로직...
지금은 Staff와 User라 if문 중첩이 적었지만 만약 백개 천개 만개라면? 가독성이 떨어지는 코드가 작성된다.
중첩 if를 제거하기 위해서 다음과 같이 코드를 작성할 수 있다.
if(user==null){
return false;
}
if(staff==null){
return false;
}
user.forEach((u)->{
.....
하지만 이렇게 하면 return 부분이 많아 유지보수가 어려워 진다는 단점이 있다.
이러한 문제를 해결하고자 Optional이 나왔다.
Optional은 클래스를 한번 더 감싸는 것으로 클래스의 인스턴스에 바로 접근하는 게 아니라 optional을 통해 접근하여 null로 인해 발생하는 문제들을 줄이고자 한다.
Optional에 인스턴스를 감싸는 방법
1. 빈 Optional은 Optional.empty()로 생성 가능
2. Null 값이 안되는 Optional 값을 생성할 때는 Optionlal.of(인스턴스)
3. Null 값이 허용되는 Optional 값을 생성할 때 Optional.ofNullable(인스턴스)
다음은 Optional이 사용되는 예시이다
public class Person{
private Optional<House> home;
public Optional<House> getHouse(){
return this.home;
}
}
~*~
public class House{
private Optional<Insurance> fireInsurance;
public Optional<Insurance> getHomeInsurance(){
return fireInsurance;
}
}
~*~
public class Insurance{
private String name;
public String getName(){
return name;
}
}
보험회사에서 사람이 가지고 있는 보험의 이름을 가져오고자 한다. 만약 Optional을 사용하지 않고 가져온다면 다음과 같이 코드를 작성해야 할 것이다.
public String getInsuranceFromPerson(Person person){
if(person!=null){
if(person.getHouse()!=null){
if(person.getHouse().getHomeInsurance()!=null){
return person.getHouse().getHomeInsurance().getName();
}
}
}
return "No Insurance"
}
각 변수들의 Null값을 체크해 주어야 하기 때문에 가독성도 떨어지고 코드가 복잡해졌다.
Optional을 사용한다면 어떻게 변할까?
Optional에도 Stream과 비슷한 map 함수가 있다. Optional의 map은 Stream의 map이 한개 이상의 결과값을 내놓는 것과 달리 한개 이하의 값을 내놓는 다는 점을 제외하면 똑같다 생각하면된다.
그렇다면 다음과 같이 코드를 작성할 수 있지 않을까?
person.map(Person::getHouse).
map(House::getHomeInsurance).
map(Insurance::getName);
그렇지 않다! map의 경우 Optional로 값을 감싸 반환하기 때문에 집을 가져올 수 있어도 두번째 결과가 Optional<Optioanl< house >>이 되기 때문에 다른 방법이 필요하다. 비슷한 문제가 stream에서도 있었다. 최소 단위로 쪼갤 수 있는 그것은 바로
flatMap
이다. Optional 값을 내보낼 때 이미 Optional에 감싸져 있다면 따로 Optional로 한번 더 감싸지는 않고 1차원 Optional의 결과 값을 내보낸다.
그럼 저기서 값은 어떻게 가져올까?
위에서는 값이 없을 시 No Insurance를 반환하게 하고 싶으니
person.map(Person::getHouse).
map(House::getHomeInsurance).
map(Insurance::getName).
orElse("No Insurance");
를 해주면 된다.
Optional을 Stream에서 사용할 때 Stream< Optional< T >>를 Stream< T >로 바꿔주고 싶으면 Optional::Stream을 이용하면 된다.
Optional에도 filter가 있다! 값이 조건에 맞으면 해당 값을 내놓고 맞지 않으면 빈 Optional을 내놓는다. 다음은 예시 코드이다
pizza.filter(p->p.hasNoCheese()).ifPresen((p)->{system.out.println("has Cheese")});
해당 값은 pizza 인스턴스가 값이 존재해도 filter를 통과하지 못하면 없는 것과 똑같은 결과가 나오는 예시 코드이다.
이제 Null의 지옥에서 벗어났다! 자세한 내용은
Optional 공식문서에서 확인하도록 하자