[Java] Java Optional (자바 옵셔널) 정리, 예제모음

2021. 3. 29. 12:57 JAVA/Java

| Java Optional( 자바 옵셔널 )

 

Java Optional 클래스 Java 8에서 추가되었으며 자바의 고질적인 문제인 NullpointerException 문제를 해결할 수 있는 방법을 제공합니다.

import java.util.Optional;

 

| of, ofNullable로 객체 감싸기

 

자바에서 제공하는 객체를 Optional 객체로 감싸기 위해서는 Optional 에서 제공하는 of  ofNullable 매서드를 사용합니다. 둘의 차이점은 of는 인자로서 null값을 받지 않는다는 것이고 ofNullable은 null값을 허용한다는 것입니다.

@Test
public void givenNonNull_whenCreatesNonNullable() {
	String name = "saelobi";
	Optional<String> opt = Optional.of(name);
	assertEquals("Optional[saelobi]", opt.toString());
}

 

아래 코드를 보시면 null값을 of 메서드의 입력으로 받을 시 NullPointerException을 일으킵니다.

@Test(expected = NullPointerException.class)
public void givenNull_whenThrowsErrorOnCreate() {
	String name = null;
	Optional<String> opt = Optional.of(name);
}

 

ofNullable은 일반 객체뿐만 아니라 null값까지 입력으로 받을 수 있다는 것을 아래 코드로 확인해 볼 수 있습니다.

@Test
public void givenNonNull_whenCreatesNullable() {
	String name = "saelobi";
	Optional<String> opt = Optional.ofNullable(name);
	assertEquals("Optional[saelobi]", opt.toString());
}

 

isPresent 메서드로 현재 Optional이 보유한 값이 null인지 아닌지를 확인할 수 있습니다.

@Test
public void givenOptional_whenIsPresentWorks() {
	Optional<String> opt = Optional.of("saelobi");
	assertTrue(opt.isPresent());
	
	opt = Optional.ofNullable(null);
	assertFalse(opt.isPresent());
}

 

이 Optional 메서드를 이용하면 다음과 같은 if를 이용한 null값 체크를 대체할 수 있습니다. if를 이용한 null값 체크가 좋지 않은 이유는 크게 2가지가 있습니다.

 

1. 코드가 길어짐에 따라 코드의 가독성이 점점 떨어지게 된다

2. 각 변수마다 null값을 체크해야 되기 때문에 프로그래머의 실수를 유발할 가능성이 높아진다.

 

Optional 방식은 위의 문제를 해결하여 가독성 좋고 강건한 코드를 만드는 데 도움을 줍니다. 다음 예제를 보면 어떤 의미인지 바로 아실 수 있을겁니다.

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

 

위의 if 의 null 체크 방식을 다음과 같이 ifPresent로 간결하게 해결할 수 있습니다.

@Test
public void givenOptional_whenIfPresentWorks() {
	Optional<String> opt = Optional.of("saelobi");
	opt.ifPresent(name -> System.out.println(name.length()));
}

 

| orElse, orElseGet으로 Optional 값 가져오기

 

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

@Test
public void whenOrElseWorks() {
	String nullName = null;
	String name = Optional.ofNullable(nullName).orElse("John");
	assertEquals("John", name);
}

 

Optional에서는 값을 가져올 때 자주 사용되는 메서드 두 가지가 있습니다. orElseGet, orElse 이 두 가지 입니다. 이 메서드가 자주 사용되는 이유는 null값 체크를 할 수 있음과 동시에 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 = "TEST";
	
	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);
}

 

또한 get orElseThrow를 이용해서 Optional의 값을 얻을 수 있습니다. 하지만 이런 방식은 기존의 null을 체크하는 방식과 다른게 없거니와 오히려 Optional을 써서 타이핑만 더 치는 안 좋은 방법이라고 생각합니다. 하루 빨리 deprecated 됬으면 하는 개인적인 소망입니다.

@Test(expected = IllegalArgumentException.class)
public void whenOrElseWorks1() {
String nullName = null;
String name = Optional.ofNullable(nullName).orElseThrow(IllegalArgumentException::new);
}

@Test
public void givenOptional_whenGetsValue() {
	Optional<String> opt = Optional.of("saelobi");
	String name = opt.get();
	
	assertEquals("saelobi", name);
}

@Test(expected = NoSuchElementException.class)
public void givenOptionalWithNull_whenGetThrowsException() {
	Optional<String> opt = Optional.ofNullable(null);
	String name = opt.get();
}

 

다음은 Optional stream 메서드를 이용한 예제입니다. if문으로 처리할 로직을 Optional과 stream 메서드로 간결한 코드로 작성할 수 있거니와 null 체크도 간편하게 할 수 있다는 큰 장점이 있죠.

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://www.baeldung.com/java-optional

출처: https://engkimbs.tistory.com/646?category=790524 [새로비]