춤추는 개발자

[Java] Optional이란? 개념과 사용법 - 1 본문

Developer's_til/그외 개발 공부

[Java] Optional이란? 개념과 사용법 - 1

Heon_9u 2022. 1. 3. 23:05
728x90
반응형

 

✅ Optional이란? Optional 개념 및 사용법

 자바로 프로그래밍 하다보면 정의되지 않은 객체에 대해 NULL값을 고려하게 되는 경우가 발생한다. 안정적인 실행을 위해서는 NULL값을 처리해 NPE(NullPointerException)가 발생하지 않게 체크해야 한다. 단순한 코드라면 짧은 로직으로 처리할 수 있지만, 스케일이 커질 수록 고려해야할 변수가 많아지고, 그만큼 NULL 체크 로직이 길어지게 된다.

 

 이러한 상황을 위해 Java8에서는 Optional<T> 클래스를 도입하여 NPE를 방지할 수 있도록 도와준다. Optional<T>는 NULL이 올 수 있는 값을 감싸는 Wrapper 클래스로, NPE가 발생하지 않도록 도와준다. Optional 클래스는 아래 예제처럼 value에 값을 저장하기 때문에 NULL이더라도 바로 NPE가 발생하지 않으며, 각종 메서드를 제공한다.

public final class Optional<T> {

    private final T value;
    ...
    
}

 

✅ Optional로 NULL 체크 대체하기

 Optional은 Wrapper 클래스이기 때문에 빈 값이 올 수도 있으며, 빈 객체는 empty() 메서드로 생성할 수 있다. 만약, NULL이 올 수 있는 데이터라면 해당 값을 Optional로 감싸서 생성할 수 있다. 또한, orElse 또는 orElseGet 메서드로 NULL인 경우라도 다른 값을 리턴할 수 있다.

Optional<String> optionalEmpty = Optional.empty();
System.out.println(optionalEmpty);
System.out.println(optionalEmpty.isPresent()); // false

Optional<String> opt = Optional.ofNullable(Object.getName());
String name = opt.orElse("isNull");

 

 이외에도 아래와 같이 NULL 검사를 한 이후, NULL일 경우, 새로운 객체를 생성해주는 경우가 있다. 이러한 과정을 코드로 나타내면 다소 번잡해보이지만, Optional<T>Lambda를 이용하면 해당 과정을 간단하게 표현할 수 있다.

List<String> names = Object.getAllNames();
List<String> checkNames = names != null ? names : new ArrayList<>();

// Optional + Lambda
List<String> names = Optional.ofNullable(getAllNames()).orElseGet(() -> new ArrayList<>());

[ 활용 예시(1) ]

public String getSubName(UserVO userVO) {
    
    if(userVO != null) {
        Master master = userVO.getMaster();
        if(master != null) {
            String subName = master.getSubName();
            if(subName != null) {
                return subName;
            }
        }
    }

    return "none sub"
}

 만약, 위처럼 특정 객체(UserVO)의 서브Name(subName)를 구하기 위한 NULL 검사 코드가 있다고 가정한다. 3중첩의 if문을 보면 당장이라도 리팩토링이 하고싶을 것이다. 해당 코드에 Optional을 적용하면 아래와 같다.

public String getSubName() {
    Optional<UserVO> userVO = Optional.ofNullable(Object.getUser());
    Optional<Master> master = userVO.map(UserVO::getMaster);
    Optional<String> subName = master.map(Master::getSubName);
    String result = subName.ofElse("none sub");

    // 위의 코드를 아래와 같이 축약
    String result = Object.map(UserVO::getMaster)
                            .map(Master::getSubName)
                            .orElse("noneSub");
}

[ 활용 예시(2) ]

String name = Object.getName();
String result = "";

try {
    result = name.toUpperCase();
} catch (NullPointerException NPE) {
    throw new CustomUpperCaseException();
}

 이번에는 name 변수를 대문자로 변경하는 코드에서 NPE처리를 해주는 로직이다. 객체의 메서드를 호출하고 try-catch문까지 포함된 위 코드는 가독성이 떨어지지만, Optional을 활용하면 아래와 같이 리팩토링할 수 있다.

 

Optional<String> nameOpt = Optional.ofNullable(Object.getName());
String result = nameOpt.orElseThrow(CustomUpperCaseException::new)
                        .toUpperCase();

[ Summary ]

 Optional은 NULL 또는 실제 값을 value로 갖는 데이터를 Wrapper로 감싸서 NPE를 방지하는 Wrapper 클래스다. 하지만, Optional은 데이터를 Wrapping하고 다시 풀고, NULL일 경우에는 대체 함수를 호출하는 등의 오버헤드가 있으므로 성능이 저하될 수 있다.

 그래서 메서드의 반환 값이 절대 NULL이 아니라면 Optional을 사용하지 않는 것이 성능 저하가 적다. 즉, Optional은 메서드의 결과가 NULL이 될 수 있으며, 클라이언트가 이 상황을 처리해야 할 때 사용하는 것이 효율적이다.

 

✅ Optional의 orElse와 orElseGet의 차이

 Optional API의 단말 연산에는 orElseorElseGet메서드가 있다. 두 함수의 차이는 아래와 같으며, 예제 코드를 통해 확인해본다.

  • orElse: Optional 안의 값이 NULL이든 아니든 항상 호출.
  • orElseGet: Optional 안의 값이 NULL일 경우에만 호출.

[ orElse와 orElseGet의 비교 코드 ]

public void findMasterID() {
    String masterID = "Empty";
    String result = Optional.ofNullable(masterID).orElse(getMasterID());
    System.out.println(result);

    masterID = "Empty";
    result = Optional.ofNullable(masterID).orElseGet(this::getMasterID);
    System.out.println(result);
}

public String getMasterID() {
    System.out.println("master ID is ");
    return "Heon9u";
}

/*
masterID is
Empty
Empty
*/

 

 출력 결과를 보면 orElse의 경우, masterID가 NULL이든 아니든 호출되기 때문에 getMasterID()가 호출됐다. 하지만, orElseGet()의 경우에는 NULL일 때만 해당 연산이 진행되므로 getMasterID()가 호출되지 않아 "master ID is"가 출력되지 않았다.

 또한 위의 코드에서 orElse는 value(값)를 취하고, orElseGet은 Supplier를 취하기 때문에 각 매개변수가 다르게 사용되었다.

 

[ orElse와 orElseGet 사용 정리 ]

  • orElse
    • Optional의 값이 NULL이든 아니든 항상 호출.
    • 그에 따라 Unique한 객체들에는 주의해서 사용해야 한다.
    • 값이 미리 존재하는 경우에 사용한다.
  • orElseGet
    • Optional의 값이 NULL일 경우에만 호출.
    • 값이 미리 존재하지 않는 거의 대부분의 경우에 사용한다.

 

✅ References

https://hbase.tistory.com/212

https://mangkyu.tistory.com/70

https://mangkyu.tistory.com/203

https://homoefficio.github.io/2019/10/03/Java-Optional-%EB%B0%94%EB%A5%B4%EA%B2%8C-%EC%93%B0%EA%B8%B0/

 

728x90
반응형