본문 바로가기
Java 웹 프로그래밍

Java 8 Null과 Optional - 2

by irerin07 2020. 1. 18.
728x90

Java 8 Null과 Optional - 1 - https://irerin07.tistory.com/107

 

자바 9의 Optional 페이지를 보면 다음과 같은 글이 있다

API Note: Optional is primarily intended for use as a method return type where there is a clear need to represent "no result, " and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.

 

Optional은 "결과 없음"을 나타내야 하지만 null을 사용할 경우 에러를 발생시킬 수 있을지도 모르는 상황에서의

메서드 리턴 타입으로 사용되도록 의도되어 만들어졌습니다.

변수의 타입이 Optional인 경우 절대로 null이 되어서는 안 되며 언제나 Optional 인스턴스를 가리키고 있어야 합니다.

 

이전 포스팅에도 적었지만 Optional은 Haskell의 Maybe에서 아이디어를 따 왔지만 Maybe처럼 사용하는 것이 아니었다.

하지만 수많은 개발자들이 이를 무시하고 Maybe처럼 사용을 했고 Java 9 문서에 위와 같은 글귀를 적어두었다.

그럼 Optional을 의도대로 사용하려면 어떻게 해야 좋을까?

 

Optional을 사용하는 26가지 방법

https://dzone.com/articles/using-optional-correctly-is-not-optional

Item 1: Optional variable에는 null을 사용하지 않는다.

//AVOID
public Optional<Cart> fetchCart() {
    Optional<Cart> emptyCart = null;
}
// PREFER
public Optional<Cart> fetchCart() {
    Optional<Cart> emptyCart = Optional.empty();
}

Optional에 null값을 넣는 것은 무의미하다. 대신 Optional.empty()를 사용하자.

Item 2: Optional.get()을 사용하기 전에 값이 들어있는지 확인한다.

// AVOID
Optional<Cart> cart = ... ; // 비어있는 cart
...
// 만약 cart가 비어있다면 NoSuchElementException이 발생한다.
Cart myCart = cart.get();
// PREFER
if (cart.isPresent()) {
    Cart myCart = cart.get();
    ... // myCart를 조작하는 코드
} else {
    ... // cart.get()을 호출하지 않는 코드
}

- isPresent() - get()을 연달아 사용하는 것은 사실 그렇게 좋은 방법은 아니지만 이 방법을 사용하기로 했다면 꼭 isPresent()를 사용해 값이 들어있는지 확인을 하다.

- get() 메서드를 없애버리자는 의견이 많이 나오고 있다. Brian Goetz도 get을 바꾸거나 없애버리는 쪽을 더 지지한다고 한다.

Item 3: 아무런 값이 없을 때는, Optional.orElse() 메서드를 통해 미리 생성한 Object를 반환하도록 한다.

// AVOID
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 비어있는 Optionial을 반환한다
    if (status.isPresent()) {
        return status.get();
    } else {
        return USER_STATUS;
    }
}
// PREFER
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
    return status.orElse(USER_STATUS);
}

- orElse() 메서드는 isPresent()-get()을 사용하는 것보다 훨씬 우아하게 value를 set 하고 반환할 수 있게 해 준다.

- 다만 orElse()의 경우 Optional이 값을 가지고 있다고 하더라도 orElse()가 가지는 매개변수(이 경우엔 USER_STATUS)를 여전히 확인한다는 것이다. 사용하지도 않을 값을 확인하는 것은 분명 성능적으로 나쁜 일이다.

- orElse()를 사용하고자 한다면 orElse()에 들어가는 매개변수(USER_STATUS)를 미리 생성한 다음 사용하도록 하자.

- 위와 같은 경우가 아니라면 item 4를 참고하자.

Item 4: 아무런 값이 없을 때는, Optional.orElseGet() 메서드를 통해 존재하지 않는 Default Object를 반환하도록 한다.

// AVOID
public String computeStatus() {
    ... // status를 설정하는 코드
}
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
    if (status.isPresent()) {
        return status.get();
    } else {
        return computeStatus();
    }
}
// AVOID
public String computeStatus() {
    ... // status를 설정하는 코드
}
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
    // status가 비어있지 않아도  computeStatus()를 호출한다.
    return status.orElse(computeStatus()); 
}
// PREFER
public String computeStatus() {
    ... // status를 설정하는 코드
}
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 비어있는  Optional을 반환한다
    // status가 비어있는 경우에만 computeStatus()를 호출한다
    return status.orElseGet(this::computeStatus);
}

Item 5: Java 10 이후부터는 아무런 값이 없을 때 orElseThrow()를 사용하여 java.util.NoSuchElementException을 발생시킨다.

// AVOID
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
    if (status.isPresent()) {
        return status.get();
    } else {
        throw new NoSuchElementException();        
    }
}
// PREFER
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
    return status.orElseThrow();
}

- isPresent()-get()을 대신하는 다른 방법으로는 orElseThrow()가 있다.

- Optional에 값이 없을 때 그냥 단순히 NoSuchElementException만 발생시킨다.

- 하지만 위의 방법은 Java 10부터 사용이 가능하며 Java 8, 9 버전은 다음 방법을 사용할 수 있다.

Item 6: Java 8, 9 에서는 아무런 값이 없을 때, orElseThrow(Supplier <? extends X> exceptionSupplier)를 사용하여 원하는 예외를 발생시킨다.

// AVOID
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
    if (status.isPresent()) {
        return status.get();
    } else {
        throw new IllegalStateException(); 
    }
}
// PREFER
public String findUserStatus(long id) {
    Optional<String> status = ... ; // 비어있는 Optional을 반환한다.
    return status.orElseThrow(IllegalStateException::new);
}

- 자바 8, 9 버전에서 isPresent()-get() 콤비 대신 사용할 수 있는 좋은 방법이다.

- Optional에 값이 없을 때 그냥 단순히 원하는 예외를 발생시키는 방법이다.

- 자바 10 에서는 orElseThrow()를 사용하면 자동으로 NoSuchElementException을 발생시켜주지만 8과 9 버전은 원하는 예외를 직접 설정해줘야 한다.

 

Item 7: Optional을 사용 중이지만 null 참조를 사용해야만 하는 경우 orElse(null)을 사용한다.

 

// AVOID
Method myMethod = ... ;
...
// Myclass의 인스턴스를 가지고 있거나 myMethod가 static인 경우 비어있게 된다.
Optional<MyClass> instanceMyClass = ... ;
...
if (instanceMyClass.isPresent()) {
    myMethod.invoke(instanceMyClass.get(), ...);  
} else {
    myMethod.invoke(null, ...);  
}
// PREFER
Method myMethod = ... ;
...
// MyClass의 instance를 가지고 있거나 myMethod가 static인 경우 비어있게 된다.
Optional<MyClass> instanceMyClass = ... ;
...
myMethod.invoke(instanceMyClass.orElse(null), ...);

- Optional을 사용 중이지만 null참조를 해야만 하는 경우가 아니라면 사용하지 말자.

- ofElse(null)을 사용하는 가장 흔한 경우는 Optional을 사용하는 동시에 특정 상황에서 null을 받아들이는 메서드를 호출하는 경우가 있다.

- 예를 들어 자바 Reflection 중 Method.invoke()의 가장 첫 번째 인자는 호출하고자 하는 객체를 받고자 한다. 하지만 만약 호출하고자 하는 메서드의 객체가 static이라면 null을 전달해야 하고, 이런 경우 orElse(null)을 사용한다.

Item 8: Optional이 비어있지 않고 들어 있는 값을 바로 사용하고자 한다면 ifPresent()를 사용한다.

// AVOID
Optional<String> status = ... ;
...
if (status.isPresent()) {
    System.out.println("Status: " + status.get());
}
// PREFER
Optional<String> status ... ;
...
status.ifPresent(System.out::println);

- 누누이 말하지만 isPresent()-get()을 같이 쓰는 것은 썩 좋은 방법은 아니다.

- Optional이 비어있다면 아무 작업도 하지 않고, 비어있지 않은 Optional의 값을 바로 사용하고자 한다면 ifPresent()를 사용한다.

 

Item 9: Java 9부터 Optional 값이 있는지 없는지에 따라 다른 코드를 실행하고자 한다면 ifPresentOrElse()를 사용한다.

// AVOID
Optional<String> status = ... ;
if(status.isPresent()) {
    System.out.println("Status: " + status.get());
} else {
    System.out.println("Status not found");
}
// PREFER
Optional<String> status = ... ;
status.ifPresentOrElse(
    System.out::println, 
    () -> System.out.println("Status not found")
);

- 자바 9 버전부터 지원되는 기능

- Optional에 값이 있는지 없는지에 따라 실행하는 코드를 다르게 할 수 있다.

 

Item 10: 자바 9부터 Optional 값의 유무에 따라 다른 Optional을 반환하는 기능을 제공한다.

// AVOID
public Optional<String> fetchStatus() {
    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");
    if (status.isPresent()) {
        return status;
    } else {
        return defaultStatus;
    }  
}
// AVOID
public Optional<String> fetchStatus() {
    Optional<String> status = ... ;
    return status.orElseGet(() -> Optional.<String>of("PENDING"));
}
// PREFER
public Optional<String> fetchStatus() {
    Optional<String> status = ... ;
    Optional<String> defaultStatus = Optional.of("PENDING");
    return status.or(() -> defaultStatus);
    // defaultStatus를 선언하지 않은 경우에는
    return status.or(() -> Optional.of("PENDING"));
}
public class GFG { 
  
    public static void main(String[] args) 
    { 
  
        // create a Optional 
        Optional<Integer> op 
            = Optional.of(9455); 
  
        // print supplier 
        System.out.println("Optional: "
                           + op); 
  
        // or supplier 
        System.out.println("Optional by or(() ->"
                           + " Optional.of(100)) method: "
                           + op.or(() -> Optional.of(100))); 
    } 
} 
Optional: Optional[9455]
Optional by or(() -> Optional.of(100)) method: Optional[9455]
public class GFG { 
  
    public static void main(String[] args) 
    { 
  
        // create a Optional 
        Optional<Integer> op 
            = Optional.empty(); 
  
        // print supplier 
        System.out.println("Optional: "
                           + op); 
  
        try { 
  
            // or supplier 
            System.out.println("Optional by or(() ->"
                               + " Optional.of(100)) method: "
                               + op.or(() -> Optional.of(100))); 
        } 
        catch (Exception e) { 
            System.out.println(e); 
        } 
    } 
} 
Optional: Optional.empty
Optional by or(() -> Optional.of(100)) method: Optional[100]

- Optional이 비어있지 않은 경우 해당 Optional을 반환하고 Optional이 비어있는 경우엔 다른 Optional을 반환하고자 할 때 사용한다.

- orElse() 혹은 orElseGet() 메서드는 Optional 내부에 들어있던 값을 반환하고 Optional을 반환하는 것이 아니기 때문에 이런 기능을 수행하지 못한다.

 

Item 11: Optional.orElse/ orElseXXX는 isPresent()-get() 보다 람다식을 사용하기에 좋다.

 

// AVOID
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";
}
// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst();
return product.map(Product::getName)
    .orElse("NOT FOUND");
// PREFER
List<Product> products = ... ;
return products.stream()
    .filter(p -> p.getPrice() < price)
    .findFirst() //findFirst()는 Optional을 반환한다.
    .map(Product::getName)
    .orElse("NOT FOUND");
// AVOID
Optional<Cart> cart = ... ;
Product product = ... ;
...
if(!cart.isPresent() || 
   !cart.get().getItems().contains(product)) {
    throw new NoSuchElementException();
}
// PREFER
Optional<Cart> cart = ... ;
Product product = ... ;
...
cart.filter(c -> c.getItems().contains(product)).orElseThrow();

- findFirst(), findAny(), reduce()등 여러 메서드들은 Optional을 반환한다. 이렇게 반환되는 Optional을 isPresent()-get()을 사용하여 처리하기엔 너무 효율성이 떨어진다.(if를 사용하거나 람다식을 분해하거나..)

- 이런 경우 orElse() 혹은 orElseXXX()를 사용하면 훨씬 깔끔하게 처리할 수 있다.

 

Item 12: Optional의 메서드들을 한 번에 여러 개 사용하지 않는 것이 좋다.

// AVOID
public String fetchStatus() {
    String status = ... ;
    return Optional.ofNullable(status).orElse("PENDING");
}
// PREFER
public String fetchStatus() {
    String status = ... ;
    return status == null ? "PENDING" : status;
}

- 값을 하나 얻기 위해서 Optional의 메서드들을 쓸데없이 여러 개 사용하는 경우가 있다. 단순히 값만 얻기 위함이라면 굳이 복잡하게 쓸 이유가 없다.

 

Item 13: Optional 타입의 필드를 선언하지 않는다.

// AVOID
public class Customer {
    [access_modifier] [static] [final] Optional<String> zip;
    [access_modifier] [static] [final] Optional<String> zip = Optional.empty();
    ...
}
// PREFER
public class Customer {
    [access_modifier] [static] [final] String zip;
    [access_modifier] [static] [final] String zip = "";
    ...
}

- Optional은 필드에서 사용되기 위해 만들어지지 않았으며 Serializable을 구현하지도 않는다.

- 더욱이 Optional클래스는 자바 빈으로 사용되기 위해 만들어지지 않았다.

 

Item 14: Optional을 생성자의 인자로 사용하지 않는다.

// AVOID
public class Customer {
    private final String name;               
    private final Optional<String> postcode; 
    public Customer(String name, Optional<String> postcode) {
        this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
        this.postcode = postcode;
    }
    public Optional<String> getPostcode() {
        return postcode;
    }
    ...
}
// PREFER
public class Customer {
    private final String name;     
    private final String postcode; 
    public Cart(String name, String postcode) {
        this.name = Objects.requireNonNull(name, () -> "Name cannot be null");
        this.postcode = postcode;
    }
    public Optional<String> getPostcode() {
        return Optional.ofNullable(postcode);
    }
    ...
}

- Optional은 대상 객체를 한 번 감싸는 방식으로 생성된다. 만약 생성자에서 인자로 Optional을 받게 된다면 매번 Optional을 생성해 인자로 전달을 해줘야 하는 상황이 생긴다.

- 다만 위의 예제처럼 getter에서도 무조건적으로 Optional을 반환하는 것 역시 좋은 방법은 아니다. Brian Goetz 역시 이에 관련해 다음과 같이 부정적인 의견을 내비쳤다.

"I think routinely using it as a return value for getters would definitely be over-use."

 

Item 15: Setters의 인자로 Optional을 사용하지 않는다.

// AVOID
@Entity
public class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    ...
    @Column(name="customer_zip")
    private Optional<String> postcode;
     public Optional<String> getPostcode() {
       return postcode;
     }
     public void setPostcode(Optional<String> postcode) {
       this.postcode = postcode;
     }
     ...
}
// PREFER
@Entity
public class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    ...
    @Column(name="customer_zip")
    private String postcode; 
    public Optional<String> getPostcode() {
      return Optional.ofNullable(postcode);
    }
    public void setPostcode(String postcode) {
       this.postcode = postcode;
    }
    ...
}

- 도메인 모델 Entity에서도 Optional을 사용할 수 있다.

 

Item 16: 메서드의 인자로 Optional을 사용하지 않는다.

// AVOID
public void renderCustomer(Cart cart, Optional<Renderer> renderer,
                           Optional<String> name) {     
    if (cart == null) {
        throw new IllegalArgumentException("Cart cannot be null");
    }
    Renderer customerRenderer = renderer.orElseThrow(
        () -> new IllegalArgumentException("Renderer cannot be null")
    );    
    String customerName = name.orElseGet(() -> "anonymous"); 
    ...
}

renderCustomer(cart, Optional.<Renderer>of(CoolRenderer::new), Optional.empty());
// PREFER
public void renderCustomer(Cart cart, Renderer renderer, String name) {
    if (cart == null) {
        throw new IllegalArgumentException("Cart cannot be null");
    }
    if (renderer == null) {
        throw new IllegalArgumentException("Renderer cannot be null");
    }
    String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
    ...
}
// call this method
renderCustomer(cart, new CoolRenderer(), null);

- Optional을 인자로 사용하는것은 생산적이지 못하다. 

- Optional이 메서드의 인자로 사용되면 메소드 내부에서 conditional logic이 발생되고, 인자를 감싸는 일은 그다지 좋은 것이 아니다.

https://stackoverflow.com/questions/31922866/why-should-java-8s-optional-not-be-used-in-arguments

 

// PREFER
public void renderCustomer(Cart cart, Renderer renderer, String name) {
    Objects.requireNonNull(cart, "Cart cannot be null");        
    Objects.requireNonNull(renderer, "Renderer cannot be null");        
    String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
    ...
}
// call this method
renderCustomer(cart, new CoolRenderer(), null);
// PREFER
// write your own helper
public final class MyObjects {
    private MyObjects() {
        throw new AssertionError("Cannot create instances for you!");
    }
    public static <T, X extends Throwable> T requireNotNullOrElseThrow(T obj, 
        Supplier<? extends X> exceptionSupplier) throws X {       
        if (obj != null) {
            return obj;
        } else { 
            throw exceptionSupplier.get();
        }
    }
}
public void renderCustomer(Cart cart, Renderer renderer, String name) {
    MyObjects.requireNotNullOrElseThrow(cart, 
                () -> new IllegalArgumentException("Cart cannot be null"));
    MyObjects.requireNotNullOrElseThrow(renderer, 
                () -> new IllegalArgumentException("Renderer cannot be null"));    
    String customerName = Objects.requireNonNullElseGet(name, () -> "anonymous");
    ...
}
// call this method
renderCustomer(cart, new CoolRenderer(), null);

 

Item 17: Optional을 비어있는 Collections나 배열을 반환하는 용도로 사용하지 않는다.

// AVOID
public Optional<List<String>> fetchCartItems(long id) {
    Cart cart = ... ;    
    List<String> items = cart.getItems(); // this may return null
    return Optional.ofNullable(items);
}
// PREFER
public List<String> fetchCartItems(long id) {
    Cart cart = ... ;    
    List<String> items = cart.getItems(); // this may return null
    return items == null ? Collections.emptyList() : items;
}

- Collections.emptyList(),   emptyMap(), and emptySet() 등을 사용하도록 합시다.

 

Item 18: Collections에 Optional을 사용하지 않는다.

// AVOID
Map<String, Optional<String>> items = new HashMap<>();
items.put("I1", Optional.ofNullable(...));
items.put("I2", Optional.ofNullable(...));
...
Optional<String> item = items.get("I1");
if (item == null) {
    System.out.println("This key cannot be found");
} else {
    String unwrappedItem = item.orElse("NOT FOUND");
    System.out.println("Key found, Item: " + unwrappedItem);
}
//PREFER
Map<String, String> items = new HashMap<>();
items.put("I1", "Shoes");
items.put("I2", null);
...
// get an item
String item = get(items, "I1");  // Shoes
String item = get(items, "I2");  // null
String item = get(items, "I3");  // NOT FOUND
private static String get(Map<String, String> map, String key) {
  return map.getOrDefault(key, "NOT FOUND");
}

- Optional은 객체이다. 이 역시도 메모리를 잡아먹고 GC의 대상이다. 성능적으로도 절대 추천하지 못할 방법이다.

 

Item 19: Optional.of() 와 Optional.ofNullable()을 잘 구분해야한다.

// AVOID
public Optional<String> fetchItemName(long id) {
    String itemName = ... ; // this may result in null
    ...
    return Optional.of(itemName); // this throws NPE if "itemName" is null :(
}
// PREFER
public Optional<String> fetchItemName(long id) {
    String itemName = ... ; // this may result in null
    ...
    return Optional.ofNullable(itemName); // no risk for NPE    
}

- 차이는 단순하다. Optional.of(null)은 NullPointerException을 발생시키고 Optional.ofNullable(null)은 Optional.empty를 반환한다. 

- null값이 들어왔을 경우 어떻게 할것인지 생각해봐야 한다. of(null)은 예외를 발생시키고 ofNullable(null)은 프로그램이 계속 진행될것이다.

 

Item 20: Optional <T>사용을 자제하고 OptionalInt, OptionalLong, 그리고 OptionalDouble을 사용한다.

// AVOID
Optional<Integer> price = Optional.of(50);
Optional<Long> price = Optional.of(50L);
Optional<Double> price = Optional.of(50.43d);
// PREFER
OptionalInt price = OptionalInt.of(50);           // unwrap via getAsInt()
OptionalLong price = OptionalLong.of(50L);        // unwrap via getAsLong()
OptionalDouble price = OptionalDouble.of(50.43d); // unwrap via getAsDouble()

- 무조건 Optioanl<T>를 사용하지 말라는 것이 아니다. 

- 객체를 감싸고 다시 푸는것은 비용이 들어간다. 그러므로 Int, Long, Double같은 원시타입의 값을 사용할 때는 OptionalInt,OptionalLong , 그리고 OptionalDouble을 사용하도록 하자.

 

Item 21: assertEquals를 사용할 때 Optional을 그대로 사용한다.

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (!(obj instanceof Optional)) {
        return false;
    }
    Optional<?> other = (Optional<?>) obj;
    return Objects.equals(value, other.value);
}
// AVOID
Optional<String> actualItem = Optional.of("Shoes");
Optional<String> expectedItem = Optional.of("Shoes");        
assertEquals(expectedItem.get(), actualItem.get());
// PREFER
Optional<String> actualItem = Optional.of("Shoes");
Optional<String> expectedItem = Optional.of("Shoes");        
assertEquals(expectedItem, actualItem);

- Optional값을 비교할 때 굳이 get등을 사용해 값을 가져올 필요가 없다.

- Optional equals() 메서드는 Optional을 비교하는것이 아닌 Optional로 감싸진 값을 비교하기 때문이다.

 

Item 22: Map() 과 flatMap()을 사용하여 값을 변환한다.

Map()사용시

// AVOID
Optional<String> lowername ...; // may be empty
// transform name to upper case
Optional<String> uppername;
if (lowername.isPresent()) {
    uppername = Optional.of(lowername.get().toUpperCase());
} else {
    uppername = Optional.empty();
}
// PREFER
Optional<String> lowername ...; // may be empty
// transform name to upper case
Optional<String> uppername = lowername.map(String::toUpperCase);

 

// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst();
String name;
if (product.isPresent()) {
    name = product.get().getName().toUpperCase();
} else {
    name = "NOT FOUND";
}
// getName() return a non-null String
public String getName() {
    return name;
}
// PREFER
List<Product> products = ... ;
String name = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst()
    .map(Product::getName)
    .map(String::toUpperCase)
    .orElse("NOT FOUND");
// getName() return a String
public String getName() {
    return name;
}

 

flatMap()사용시

// AVOID
List<Product> products = ... ;
Optional<Product> product = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst();
String name = null;
if (product.isPresent()) {
    name = product.get().getName().orElse("NOT FOUND").toUpperCase();
}
// getName() return an Optional
public Optional<String> getName() {
    return Optional.ofNullable(name);
}
// PREFER
List<Product> products = ... ;
String name = products.stream()
    .filter(p -> p.getPrice() < 50)
    .findFirst()
    .flatMap(Product::getName)
    .map(String::toUpperCase)
    .orElse("NOT FOUND");
// getName() return an Optional
public Optional<String> getName() {
    return Optional.ofNullable(name);
}

- map()은 값에 함수 인자(function argument)를 적용한 뒤 결과를 Optional로 감싸서 반환한다.

- flatMap()은 Optional 값에 적용되는 함수 인자를 받아 결과를 Optional로 감싸지 않고 그대로 반환한다.

 

Item 23: filter()를 사용해서 값을 걸러낸다

/ AVOID
public boolean validatePasswordLength(User userId) {
    Optional<String> password = ...; // User password
    if (password.isPresent()) {
        return password.get().length() > 5;
    }
    return false;
}
// PREFER
public boolean validatePasswordLength(User userId) {
    Optional<String> password = ...; // User password
    return password.filter((p) -> p.length() > 5).isPresent();
}

 

Item 24: 자바 9부터 Optional API와 Stream API를 같이 써야한다면 Optional.stream()을 사용한다.

// AVOID
public List<Product> getProductList(List<String> productId) {
    return productId.stream()
        .map(this::fetchProductById)
        .filter(Optional::isPresent)
        .map(Optional::get)
        .collect(toList());
}
public Optional<Product> fetchProductById(String id) {
    return Optional.ofNullable(...);
}
// PREFER
public List<Product> getProductList(List<String> productId) {
    return productId.stream()
        .map(this::fetchProductById)
        .flatMap(Optional::stream)
        .collect(toList());
}
public Optional<Product> fetchProductById(String id) {
    return Optional.ofNullable(...);
}

- 자바 9에서부터 Optional.stream() 메서드를 사용해 Optional 인스턴스를 stream처럼 다룰 수 있고 Stream의 메서드들도 사용할 수 있다.

- Optional.stream()은 filter()와 map()을 같이 쓰는대신 flatMap()만을 사용할 수 있다.

public static <T> List<T> convertOptionalToList(Optional<T> optional) {
    return optional.stream().collect(toList());
}

 

- 추가적으로 Optional을 List로 변경할 수도 있다

 

Item 25: Optional에는 Identity-Sensitive Operations을 사용하지 않도록 한다.

* Identity-Sensitive Operations - '==', identity hash code 혹은 synchronization

// AVOID
Product product = new Product();
Optional<Product> op1 = Optional.of(product);
Optional<Product> op2 = Optional.of(product);
// op1 == op2 => false, expected true
if (op1 == op2) { ...
// PREFER
Product product = new Product();
Optional<Product> op1 = Optional.of(product);
Optional<Product> op2 = Optional.of(product);
// op1.equals(op2) => true,expected true
if (op1.equals(op2)) { ...

- Optional은 value-based 클래스이다.

- https://docs.oracle.com/javase/8/docs/api/java/lang/doc-files/ValueBased.html

// NEVER DO
Optional<Product> product = Optional.of(new Product());
synchronized(product) {
    ...
}

 

Item 26: Optional이 비어있다면 Boolean을 반환한다. Java 11부터는 Optional.isEmpty()를 사용한다.

// AVOID (Java 11+)
public Optional<String> fetchCartItems(long id) {
    Cart cart = ... ; // this may be null
    ...    
    return Optional.ofNullable(cart);
}
public boolean cartIsEmpty(long id) {
    Optional<String> cart = fetchCartItems(id);
    return !cart.isPresent();
}
// PREFER (Java 11+)
public Optional<String> fetchCartItems(long id) {
    Cart cart = ... ; // this may be null
    ...    
    return Optional.ofNullable(cart);
}
public boolean cartIsEmpty(long id) {
    Optional<String> cart = fetchCartItems(id);
    return cart.isEmpty();
}

- 자바 11 부터는 Optional.isEmpty()를 사용해 Optional이 비어있는 경우 true를 반환할 수 있다. 

728x90