Daily Notebook

[Java] Java Optional

by 배부른코딩로그

Optional 클래스는 고질적인 문제인 NullpointerException를 해결하기 위해 추가되었다.

import java.util.Optional;

 

of, ofNullable로 객체 감싸기

Optional로 감싸기 위해서는 Optional에서 static 메서드로 제공하는 ofofNullable 를 사용할 수 있다.

- Optional.of(T value)는 인자로서 null값을 허용하지 않는다.

@Test
public void optional_of_with_not_null() {
  Optional<String> optionalName = Optional.of("배부른코딩로그");

  assertEquals("Optional[배부른코딩로그]", optionalName.toString());
}
@Test
public void optional_of_with_null() {
  Optional<String> optionalNullValue = Optional.of(null);

  assertThrows(NullPointerException.class, () -> {
    optionalNullValue.toString();
  });
}

- Optional.ofNullable(T value)은 null값을 허용한다.

@Test
public void optional_ofNullable_with_not_null() {
  Optional<String> optionalName = Optional.ofNullable("배부른코딩로그");

  assertEquals(optionalName.toString());
}
@Test
public void describe_optional_ofNullable() {
  Optional<String> optionalNull = Optional.ofNullable(null);

  assertEquals("Optional.empty", optionalNull.toString());
}

- isPresent()를 통해 현재 Optional객체가 보유한 값이 null인지 아닌지 확인도 가능하다.

@Test
public void describe_optional_ofNullable() {
  Optional<String> optionalName = Optional.of("배부른코딩로그");

  assertTrue(optionalName.isPresent());

  Optional<String> optionalMotto = Optional.of(null);

  assertFalse(optionalMotto.toString());
}

Optional의 isPresent메서드는 아래와 같은 if를 이용한 null값 체크를 대체할 수 있다.

if (name != null) {
  System.out.println(name.substring(3));
}

if null check가 좋지 않은 이유는 크게 두 가지다

  1. 각 변수마다 null값을 체크해야 되기 때문에 개발자의 실수(NullPointerException)를 유발할 가능성이 높아진다.
  2. 코드가 길어짐에 따라 가독성이 떨어진다.

 

따라서, Optional 방식은 코드를 안전하게 만들뿐만 아니라 가독성면에서도 좋다고 볼 수 있다.

 

orElse, orElseGet 활용하기

if 에서 null값이 아닌 경우의 처리를 else 키워드로 해결하지만, Optional의 경우 orElse로 간단하게 해결할 수 있다.

@Test public void when_orElse_with_null() {
  String nullName = null;
  String name = Optional.ofNullable(nullName)
    .orElse("배부른코딩로그"); 

  assertEquals("배부른코딩로그", name);
}

Optional 값을 가져올 때 자주 사용되는 메서드는 orElseGetorElse 다. 두 메서드 모두 null값 체크와 동시에 null값일 때 추가적인 처리가 가능하다. 다만, 추가적으로 처리하는 부분이 함수인 경우 주의가 필요하다. orElseGet은 Optional 값이 null일 경우에만 orElseGet에 주어진 함수를 실행하지만, orElse는 null값 유무와 상관없이 실행된다. 이 부분을 신경쓰지 않으면 성능 이슈가 발생할 수 있기 때문에 주의가 필요하다.

public String getMyDefault() 
  System.out.println("Getting Default Value");
  return "Default Value"; 
}

@Test
public void whenOrElseGetAndOrElseOverLap() {
  String text = null;
  
  System.out.println("Using orElseGet:");
  String defaultText = Optional.ofNullable(text)
      .orElseGet(this::getMyDefault);

  assertEquals("Default Value", defaultText);

  System.out.println("Using orElse:");
  defaultText = Optional.ofNullable(text)
      .orElse(getMyDefault());

  assertEquals("Default Value", defaultText);
}

@Test
public void whenOrElseGetAndOrElseDiff() {
  String text = "배부른코딩로그";
  
  System.out.println("Using orElseGet:");
  String defaultText = Optional.ofNullable(text)
      .orElseGet(this::getMyDefault);

  assertEquals("TEST", defaultText);

  System.out.println("Using orElse:");
  defaultText = Optional.ofNullable(text)
      .orElse(getMyDefault());

  assertEquals("TEST", defaultText);
}

 

Optional의 유용한 예제

If 처리 로직을 Optional과 stream 메서드로 간결하게 대체 작성한 예제이다.

public class Modem {
  private Double price;

  public Double getPrice() {
    return price;
  }

  public void setPrice(Double price) {
    this.price = price;
  }

  public Modem(Double price) {
    this.price = price;
  } 
}

public boolean priceIsInRange1(Modem modem) {
  boolean isInRange = false;

  if (modem != null && modem.getPrice() != null && (modem.getPrice() >= 10 && modem.getPrice() <= 15)) {
    isInRange = true;
  }

  return isInRange;
}

@Test
public void whenFiltersWithoutOptional() {
  assertTrue(priceIsInRange1(new Modem(10.0)));
  assertFalse(priceIsInRange1(new Modem(9.9)));
  assertFalse(priceIsInRange1(new Modem(null)));
  assertFalse(priceIsInRange1(new Modem(15.5)));
  assertFalse(priceIsInRange1(null));
}

public boolean priceIsInRange2(Modem modem) {
  return Optional.ofNullable(modem)
      .map(Modem::getPrice)
      .filter(p -> p >= 10)
      .filter(p -> p <= 15)
      .isPresent();
}

@Test
public void whenFiltersWithoutOptional2() {
  assertTrue(priceIsInRange2(new Modem(10.0)));
  assertFalse(priceIsInRange2(new Modem(9.9)));
  assertFalse(priceIsInRange2(new Modem(null)));
  assertFalse(priceIsInRange2(new Modem(15.5)));
  assertFalse(priceIsInRange2(null));
}

Optional과 stream 메서드를 이용한 또 다른 예제다.

@Test
public void givenOptional_whenMapWorks() {
  List<String> companyNames = Arrays.asList( "Samsung", "SK", "NAVER", "Daum");
  
  Optional<List<String>> listOptional = Optional.of(companyNames);
  int size = listOptional.map(List::size).orElse(0);

  assertEquals(4, size);
}

@Test
public void givenOptional_whenMapWorks2() {
  String name = "saelobi";
  
  Optional<String> nameOptional = Optional.ofNullable(name);
  int len = nameOptional.map(String::length).orElse(0);

  assertEquals(7, len);
}

@Test
public void givenOptional_whenMapWorksWithFilter() {
  String password = " password ";
  
  Optional<String> passOpt = Optional.of(password);
  boolean correctPassword = passOpt.filter( pass -> pass.equals("password")).isPresent();

  assertFalse(correctPassword);

  correctPassword = passOpt .map(String::trim) .filter(pass -> pass.equals("password")) .isPresent();

  assertTrue(correctPassword);
}

 

 

출처: https://engkimbs.tistory.com/646 [새로비]

반응형

'Java' 카테고리의 다른 글

[Java] Null Safe한 자바 컬렉션 정렬  (0) 2022.05.24
[Java] Enum 소화하기  (0) 2022.05.09
[Java] YAML 활용하기  (0) 2022.05.04
[Java] @Required 직접 만들어보기  (0) 2022.04.23
[Java] Array, List, Set, Map 선언과 초기화  (1) 2021.11.05

블로그의 정보

배부른코딩로그

배부른코딩로그

활동하기