Language/Java

[Java] Optional 제대로 쓰기

p8labs 2023. 2. 6. 11:26
728x90
반응형

Optional 제대로 쓰기

Optional에 대해 공부를 해보다가 좋은 예제들이 있어서 적어봅니다.

출처를 남겼으며 나머지 내용들도 확인해 보시면 좋을것 같습니다.

 

옵셔널 변수에 널값 사용하지 않기

Optional 을 쓸때는 null 지옥을 피하기위해 사용함으로 null 값을 쓰는것은 의미가 없다.

// AVOID
public Optional<Cart> fetchCart() {

    Optional<Cart> emptyCart = null;
    ...
}

 

 

// PREFER
public Optional<Cart> fetchCart() {

    Optional<Cart> emptyCart = Optional.empty();
    ...
}

 

Get을 쓰기전에 값 존재 여부확인

// AVOID
Optional<Cart> cart = ... ; // this is prone to be empty
...
// if "cart"is empty then this code will throw a java.util.NoSuchElementException
Cart myCart = cart.get();

 

 

// PREFER
if (cart.isPresent()) {
    Cart myCart = cart.get();
    ... // do something with "myCart"
} else {
    ... // do something that doesn't call cart.get()
}

 

디폴트 값이 있다면 orElse 를 사용하자

// AVOID
public static final String USER_STATUS = "UNKNOWN";
...
public String findUserStatus(long id) {

    Optional<String> status = ... ; // prone to return an empty Optional

    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 = ... ; // prone to return an empty Optional

    return status.orElse(USER_STATUS);
}

 

orElse 대신 orElseGet 을 쓰자

orElse 같은 경우는 optional 이 비워져 있지 않아도 평가 연산을 수행한다.

// AVOID
public String computeStatus() {
    ... // some code used to compute status
}

public String findUserStatus(long id) {

    Optional<String> status = ... ; // prone to return an empty Optional

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

 

 

// PREFER
public String computeStatus() {
    ... // some code used to compute status
}

public String findUserStatus(long id) {

    Optional<String> status = ... ; // prone to return an empty Optional

    // computeStatus() is called only if "status" is empty
    return status.orElseGet(this::computeStatus);
}

 

 

값이 없는 경우 익셉션을 던지자

// AVOID
public String findUserStatus(long id) {

    Optional<String> status = ... ; // prone to return an empty Optional

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

 

 

// PREFER
public String findUserStatus(long id) {

    Optional<String> status = ... ; // prone to return an empty Optional

    return status.orElseThrow();
}

 

 

// PREFER
public String findUserStatus(long id) {

    Optional<String> status = ... ; // prone to return an empty Optional

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

 

값이 존재 할때만 메소드를 실행하고 싶은 경우

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

 

 

// PREFER
Optional<String> status ... ;
...
status.ifPresent(System.out::println);

 

값 존재 여부에 따라 처리를 해야 될때

// 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")
);

 

생성자에 Optional 값을 쓰지 말자

// AVOID
public class Customer {

    private final String name;               // cannot be null
    private final Optional<String> postcode; // optional field, thus may be null

    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;     // cannot be null
    private final String postcode; // optional field, thus may be null

    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);
    }
    ...
}

 

컬렉션 리턴 타입에 옵셔널을 쓰지말자

  • 대신 Collections.emptyList() 를 사용하자
// 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;
}

 

 

 

 

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<T> 사용을 지양하자 대신 OptionalInt, OptionalLong, or 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()

 

Asserting Equality 을 수행 할때 UnWrap을 할 필요가 없다

// 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);

 

특정값을 조건처리할때 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();
}

 

Optional 값을 비교 할때 equals 를 쓰자

// 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)) { ...

 

 

 

 

출처:

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

 

26 Reasons Why Using Optional Correctly Is Not Optional - DZone

We take a look at the top 25 most important concepts to keep in mind when working with Optionals in Java, focusing on null variables and more.

dzone.com

 

728x90
반응형