본문 바로가기

공부 정리/Java

Optional을 제대로 사용하는 26가지 방법 ( 1 )

728x90
반응형

Item 1 : null을 Optional 변수에 할당하지 말 것

//잘못된 방법
public Optional<Cart> fetchChart() {
	Optional<Cart> emptyCart = null;
    ...
}


//옳은 방법
public Optional<Cart> fetchCart() {
	Optional<Cart> emptyCart = Optional.empty();
    ...
}

Optional을 초기화하고 싶다면, null이 아니라 Optional.empty()를 사용해야 합니다. Optional은 단지 컨테이너나 박스일 뿐이고, null로 초기화시키는 것은 무의미합니다.

 

Item 2 : Optional.get()을 호출하기 이전에 Optional이 값을 가지도록 만들 것

//잘못된 방법
Optional<Cart> cart = ... ; // cart 값이 null일 수 있는 상황
...
// cart가 null이라면 NoSuchElementException 예외 발생
Cart myCart = cart.get();



//옳은 방법
if (cart.isPresent()) {
    Cart myCart = cart.get();
    ... // 계속 진행...
} else {
    ... // cart.get()은 호출하지 말것...
}

Optional.get()을 사용하기로 했다면, 호출하기 이전에 Optional 값이 존재하는지 꼭 증명해야 한다는 것을 기억해야 합니다. 전형적으로, Optional.isPresent() 검사를 통해서 확인 할 수 있습니다. isPresent()-get()의 조합은 필요 이상의 검사를 하기에 별로 추천하지 않지만, 이 방식을 택한다면 꼭 isPresent() 부분을 잊어서는 안됩니다. 그럼에도 불구하고, Optional.get()은 많은 부분에서 사용되지 않는 다는 사실을 알아두십시오.

 

Item3 : 어떠한 값도 존재하지 앟는다면, Optional.orElse()를 활용하여 미리 기본 객체를 만들어 둘 것

// 잘못된 방법
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {

    Optional<String> status = ... ; // empty Optional의 가능성 있는 경우

    if (status.isPresent()) {
        return status.get();
    } else {
        return USER_STATUS;
    }
}


// 옳은 방법
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {

    Optional<String> status = ... ; // empty Optional의 가능성 있는 경우

    return status.orElse(USER_STATUS);
}

Optional.orElse() 메서드를 사용하는 것은 isPresent()-get()을 대체합니다. 여기서 주의할 것은, 비록 Optional 값이 empty가 아니더라도, orElse()의 매개변수는 실행된다는 사실입니다. 이것은 비록 우리가 의도하지 않았더라도, 불필요한 사용을 통해 성능에 문제를 유발할 수 있습니다. 따라서 매개변수가 이미 확정되었을 때 orElse()를 사용해야 합니다.

 

Item 4 : 어떠한 값도 존재하지 않는다면, Optional.orElseGet()을 활용하여 미리 기본 객체를 만들어 둘 것

// 잘못된 방법1
public String computeStatus() {
    ... // status를 연산하는 코드들...
}

public String findUserStatus(long id) {

    Optional<String> status = ... ; // empty Optional을 반환하는 코드

    if (status.isPresent()) {
        return status.get();
    } else {
        return computeStatus();
    }
}



// 잘못된 방법2
public String computeStatus() {
    ... // status를 연산하는 코드들...
}

public String findUserStatus(long id) {

    Optional<String> status = ... ; // empty Optional을 반환하는 코드

    // status가 empty가 아닐지라도 computeStatus()가 호출된다
    return status.orElse(computeStatus()); 
}


// 옳은 방법
public String computeStatus() {
    ... // status를 연산하는 코드들..
}

public String findUserStatus(long id) {

    Optional<String> status = ... ; // empty Optional을 반환하는 코드

    // status가 empty일 때만, computeStatus()가 호출된다
    return status.orElseGet(this::computeStatus);
}

Optional.orElseGet()을 사용하는 것은 isPresent()-get() 조합을 대체하는 또다른 방법입니다. 중요한 것은, orElseGet()의 매개변수 Supplier는 Java 8의 특성입니다 이것은 Supplier 메서드가 단지 Optional에 값이 존재하지 않을 때 실행된다는 것을 의미합니다. 그래서, 객체를 생성하는 성능 저하를 일으키고 Optional에 값이 있다면 우리가 원치 않아도 코드를 실행하는 orElse()의 단점을 피할 수 있습니다.

 

Item 5 : 어떠한 값도 존재하지 않는다면, NoSuchElementException 예외 대신에 orElseThrow() 를 발생시킬 것 ( java 10 )

// 잘못된 방법
public String findUserStatus(long id) {

    Optional<String> status = ... ; // empty Optional을 반환하는 코드

    if (status.isPresent()) {
        return status.get();
    } else {
        throw new NoSuchElementException();        
    }
}

// 옳은 방법
public String findUserStatus(long id) {

    Optional<String> status = ... ; // empty Optional을 반환하는 코드

    return status.orElseThrow();
}

Optional.orElseThrow() 메서드는 isPresent()-get() 조합의 또다른 예시입니다. 가끔씩, Optional 값이 없다면, NoSuchElementException을 발생시키고 싶을 것입니다. java 10에서는 이제 orElseThrow() 메서드를 매개변수 없이 사용할 수 있습니다. java 8,9에서는 item6를 사용하시면 됩니다.

 

Item 6 : 어떠한 값도 존재하지 않는다면, 명백한 예외 처리 대신에 orElseThrow(Supplier<? extends X> exceptionSupplier) 을 발생시킬 것

// 잘못된 방법
public String findUserStatus(long id) {

    Optional<String> status = ... ; //empty Optional를 반환하는 코드

    if (status.isPresent()) {
        return status.get();
    } else {
        throw new IllegalStateException(); 
    }
}



// 옳은 방법
public String findUserStatus(long id) {

    Optional<String> status = ... ; // empty Optional를 반환하는 코드

    return status.orElseThrow(IllegalStateException::new);
}

java 10에서, orElseThrow()를 사용하라고 Item5에서 말했습니다. 

Optional.orElseThrow(Suupier<? extends X> exceptionSupplier)는 isPresent()-get() 조합을 대체하는 또다른 방법입니다. 가끔, Optional 값이 존재하지 않으면 java 8,9 버전에서는 해당 메서드를 발생시키면 됩니다.

 

Item 7 : Optional 값이 있고 null 참조가 필요하다면, orElse(null)을 사용할 것

// 잘못된 방법
Method myMethod = ... ;
...
// MyClass의 객체를 포함하거나 "myMethod"가 static이라면 empty이다.
Optional<MyClass> instanceMyClass = ... ;
...
if (instanceMyClass.isPresent()) {
    myMethod.invoke(instanceMyClass.get(), ...);  
} else {
    myMethod.invoke(null, ...);  
}  


// 옳은 방법
Method myMethod = ... ;
...
// MyClass의 객체를 포함하거나 "myMethod"가 static이라면 empty이다.
Optional<MyClass> instanceMyClass = ... ;
...
myMethod.invoke(instanceMyClass.orElse(null), ...); 

 만약 Optional을 사용하는데, null을 참조하고 싶다면 orElse(null)을 사용합니다.

orElse(null)에 대한 전형적인 시나리오는 우리가 Optional을 가지고 있고 null 참조를 해야만 하는 특별한 상황일 때 발생합니다. 예를 들어, 예제의 자바 Reflection API에서 Method.invoke()을 확인해봅시다. 이 메서드의 첫번째 매개변수는 이 특별한 메서드가 호출하게 되는 객체 인스턴스입니다.

 

Item 8 : 만약 값이 존재한다면 Optional을 소비할 것. 값이 존재하지 않는다면 아무것도 하지 말 것.(Optional.ifPresent()의 역할)

// 잘못된 방법
Optional<String> status = ... ;
...
if (status.isPresent()) {
    System.out.println("Status: " + status.get());
}

// 옳은 방법
Optional<String> status ... ;
...
status.ifPresent(System.out::println);  

Optional.ifPresent()는 만약 단순히 값을 소비하는 것이 필요할 때 isPresent()-get() 조합을 대체하는 좋은 방법입니다. 값이 존재하지 않는다면 아무것도 하지 않습니다.

 

Item 9 : 만약 값이 존재한다면 Optional을 소비할 것. 존재하지 않는다면 Empty의 행동을 실행할 것( Optional.ifPresentOrElse()역할 , java 9 )

 

// 잘못된 방법
Optional<String> status = ... ;

if(status.isPresent()) {
    System.out.println("Status: " + status.get());
} else {
    System.out.println("Status not found");
}


// 옳은 방법
Optional<String> status = ... ;

status.ifPresentOrElse(
    System.out::println, 
    () -> System.out.println("Status not found")
);

java 9에서, Optional.ifPresentOrElse()isPresent()-get() 조합을 대체하는 좋은 방법입니다. 이것은 ifPresent() 메서드와 이것을 감싸서 else를 사용하것과 비슷합니다.

 

Item 10 : 만약 값이 존재한다면, Optional을 리턴할 것. 존재하지 않는다면 Other Optional을 리턴할 것 ( Optional.or()의 역할, java 9)

// 잘못된 방법1
public Optional<String> fetchStatus() {

    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");

    if (status.isPresent()) {
        return status;
    } else {
        return defaultStatus;
    }  
}

// 잘못된 방법2
public Optional<String> fetchStatus() {

    Optional<String> status = ... ;

    return status.orElseGet(() -> Optional.<String>of("PENDING"));
}


// 옳은 방법
public Optional<String> fetchStatus() {

    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");

    return status.or(() -> defaultStatus);
    // 혹은 "defaultStatus" 변수를 사용하지 않고
    return status.or(() -> Optional.of("PENDING"));
}

가끔, empty가 아닌 Optional에 대해서 우리는 Optional을 리턴하고 싶을 때가 있습니다. 그리고 Optional이 empty이면, 우리는 Optional을 리턴하는 다른 행동을 실행시키고 싶을 수 이습니다. orElse()orElseGet() 메서드는 이 목표를 달성하지 못합니다. 왜냐하면 두개 모두 래핑되지 않는 값들을 반환하기 때문입니다. 이제 java 9에서 소개하는 Optional.or() 메서드를 통해서 값을 묘사하는 Optional 반환이 가능하니다. 그렇지 않은 경우, supplying 함수에 의해서 만들어진 Optional이 리턴됩니다.

 

Item 11 : 람다에서 Optional.orElse / orElseXXXX 를 통해서 isPresent()-get() 조합을 대체할 것

 

예시 1

// 잘못된 방법1
List<Product> products = ... ;

Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst();

if (product.isPresent()) {
    return product.get().getName();
} else {
    return "NOT FOUND";
}


// 잘못된 방법2
List<Product> products = ... ;

Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst();

return product.map(Product::getName)
    .orElse("NOT FOUND");
    

// 옳은 방법
List<Product> products = ... ;

return products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst()
    .map(Product::getName)
    .orElse("NOT FOUND");

 

예시 2

// 잘못된 방법
Optional<Cart> cart = ... ;
Product product = ... ;
...
if(!cart.isPresent() || 
   !cart.get().getItems().contains(product)) {
    throw new NoSuchElementException();
}  

// 옳은 방법
Optional<Cart> cart = ... ;
Product product = ... ;
...
cart.filter(c -> c.getItems().contains(product)).orElseThrow();

분산되는 코드 대신에 연속된 람다를 사용하면서 문제를 해결할 수 있습니다. java 9에 있는 ifPresent()ifPresentOrElse()를 고려하십시오. 또한 or() 메서드도 있습니다.

 

람다에 특화된 몇몇의 연산은 Optional 값을 리턴합니다(e.g., findFirst(), findAny(), reduce()...) isPresent()-get() 조합을 사용해서 이 Optional을 처리하는 것은 굉장히 멋없는 접근입니다. 왜냐하면 그것은 람다의 체인을 끊어버리도록 하고 fi라는 조건문으로 코드를 굉장히 오염시키기 때문입니다. 체인을 계속 연결시키는 것을 고려해야합니다. 그러한 경우에, orElse()orElseXXX()는 굉장히 손쉬운 방법입니다. 왜냐하면 그것들을 통해서 람다표현은 계속 체인으로 연결될 수 있고 잘못된 코드를 피합니다.

 

Item 12 : 값을 얻겠다는 하나의 목적을 위해 Optional 메서드를 체인으로 연결하는 것은 피할 것

// 잘못된 방법
public String fetchStatus() {

    String status = ... ;

    return Optional.ofNullable(status).orElse("PENDING");
}

// 옳은 방법
public String fetchStatus() {

    String status = ... ;

    return status == null ? "PENDING" : status;
}

때때로 우리는 과도한 사용을 합니다. 무슨 의미냐하면, Optional을 어디에서나 사용하려고 합니다. Optional의 경우 공통적인 시나리오는 값을 얻ㄴ겠다는 하나의 목적을 위해 메서드를 체인으로 연결하는 것을 포함합니다. 이러한 방법을 피하고 단순하고 직관적인 코드가 필요합니다.

 

Item 13 : Optional 타입으로 어떠한 필드도 선언하지 말 것

// 잘못된 방법
public class Customer {

    [access_modifier] [static] [final] Optional<String> zip;
    [access_modifier] [static] [final] Optional<String> zip = Optional.empty();
    ...
}


// 옳은 방법
public class Customer {

    [access_modifier] [static] [final] String zip;
    [access_modifier] [static] [final] String zip = "";
    ...
}

setter를 포함하여, 생성자의 매개변수 등 메서드에서 잘못된 Optional 사용하지 마십시오.

Optional은 필드로 사용되도록 만들어진 것도 아니고, Serializable을 구현하지도 않습니다. Optional 클래스는 명확하게 Java Bean 속성으로서 사용을 하도록 의도된 것이 아닙니다.

728x90
반응형