손영배 블로그 누구나 쉽게 이해하고 습득하기

Optional 본문

Java

Optional

손영배 2020. 1. 30. 16:05

NullPointerException 대한 처리를 (번거롭지 않게) 단순히 처리할 수 있도록 자바 8에서 Optional 클래스가 만들어졌다.

 

< Optional 클래스를 공부하면 세련된 코드를 즐겁게 작성할 수 있다. 그러나 늘 그렇듯 세련된 코드를 작성하려면 그만큼 더 알아야 한다. >

 

  • Optional 클래스의 기본적인 사용 방법

- Optional 클래스는 java.util 패키로 묶여 있으며, 다음과 같이 정의되어 있다.

 

  • Optional 인스턴스의 생성 방법

- of, ofNullable 두 가지를 이용해서

Optional<String> os1 = Optional.of(new String("Toy1"));
Optional<String> os2 = Optional.ofNullable(new String("Toy2"));

 

of, ofNullable의 차이점은 null의 허용 여부에 있다. -> ofNullable의 인자로는 null을 전달할 수 있다. (비어 있는 Optional 인스턴스를 생성할 수 있다. of 메소는 null을 인자로 전달할 수 없다. (null을 전달할 경우 NullPointerException이 발생한다.) 

 

Optional 인스턴스를 대상으로 내용물의 존재 여부를 확인할 수 있고, 또 해당 내용물을 꺼낼 수도 있다.

		if(os1.isPresent())
			System.out.println(os1.get());

 

  • Optional의 매력 람다식(또는 메소드 참조)을 이용해서 if문을 제거 할 수 있다.
import java.util.Optional;

public class StringOptional1 {
	public static void main(String[] args) {
		Optional<String> os1 = Optional.of(new String("Toy1"));
		Optional<String> os2 = Optional.ofNullable(new String("Toy2"));
		
		os1.ifPresent(s -> System.out.println(s)); //람다식 버전
		os2.ifPresent(System.out::println);  // 메소드 참조 버전
	}
}
  • ifPresent 메소드는 매개변수 형이 Consumer이다. - public void ifPresent(Consumer<? super T> consumer) -
  • 따라서 다음 메소드 accept의 구현에 해당하는 람다식 or 메소드 참조를 ifPresent 호출 시 인자로 전달해야 한다.
  • 그러면 ifPresenet가 호출 되었을 때, Optional 인스턴스가 저장하고 있는 내용물이 있으면, 이 내용물이 인자로 전달되면서 accept 메소드가 호출된. (다시 말해서 전달된 람다식이 실행된다.)
  • 반면 내용물이 없으면 아무 일도 일어나지 않는다. 
  • if문이 사라졌다는 것에는 큰 의미가 있다.

 

  • map 메소드 소개

- "apply 메소드가 반환하는 대상을 Optional 인스턴스에 담아서 반환한다"

- Optional<String> os2 = os1.map(s -> s.toUpperCase());

- 위 문장의 람다식은 String 인스턴스의 참조 값을 반환한다. 따라서 위 문장의 map이 호출되는 순간 반환형 U가 String으로 결정되어 위의 람다식은 다음 apply 메소드의 몸체를 구성하게 된다.

- 그리고 위 문장의 map이 호출되면 아래의 apply 메소드의 인자로는 참조변수 os1이 지니는 인스턴스가 전달이 된다.

- String apply ( String s ){

        return s.toUpperCase(); // 문자열의 모든 문자를 대문자로 바꿔서 반환

}

 

- 반환할 때 Optional 인스턴스로 감싸서 반환한다.

import java.util.Optional;

public class OptionalMap {

	public static void main(String[] args) {
		Optional<String> os1 = Optional.of("Optional String");
		Optional<String> os2 = os1.map(s -> s.toUpperCase());
		System.out.println(os2.get());
		
		Optional<String> os3 = os1.map(s -> s.replace(' ', '_')).map(s-> s.toLowerCase());
		
		System.out.println(os3.get());
	}
}

- os1.map(s -> s.replace(' ', '_')) 가 먼저 호출되고 "Optional_String"이 반환되고 이어서 다음 메소드를 호출한다.

 

  • Optional 클래스를 사용하면 if ~ else 문을 대신할 수 있다 : orElse 메소드의 소개

- 위에서는 if문을 사용하지 않 수 있었지만 if~else문도 사용하지 않을 수 있다.

import java.util.Optional;

public class OptionalOrElse {

	public static void main(String[] args) {
		Optional<String> os1 = Optional.empty();
		Optional<String> os2 = Optional.of("So Basic");
		
		String s1 = os1.map(s -> s.toString()).orElse("Empty"); //null이 아니라면
		String s2 = os2.map(s -> s.toString()).orElse("Empty"); //null이 아니라면
		
		System.out.println(s1);
		System.out.println(s2);
	}
}

 

- Optional os1 = Optional.empty(); -> Optional os1 = Optional.ofNullable(null); 빈 Optional 인스턴스가 생성되어 반    환된다. (ofNullable 메소드의 호출 문장과 동일하다)

 

String s1 = os1.map(s -> s.toString()).orElse("Empty");

 

os1이 참조하는 Optional 인스턴스는 비어 있다. 이러한 경우 map은 빈 Optional 인스턴스를 생성하여 반환한다. map이 반환한 빈 Optional 인스턴스를 대상으로 orElse 메소드를 호출하게 된다. 그리고 orElse를 호출하면서 전달된 인스턴스가 대신 반환된다. 즉 위의 문장이 실행되면 s1은 문자열 "Empty"를 참조하게 된다.

 

<예제>

public class MapElseOptional {

	public static void main(String[] args) {
		Optional<ConInfo> ci = Optional.of(new ConInfo(null, "Republic of Korea"));
		
		String phone = ci.map(c -> c.getPhone()).orElse("There is no phone number.");
		
		String addr = ci.map(c -> c.getAdrs()).orElse("There is no address.");
		
		System.out.println(phone);
		System.out.println(addr);
		
	}
}

 

  • Optional 클래스를 코드 전반에 사용하기 위해서는 map 메소드와 성격이 유사한 -> flatMap 메소드

Optional os2 = os1.map(s -> s.toUpperCase());

Optional os3 = os1.flatMap(s -> Optional.of(s.toLowerCase()));

 

차이점

- map과 flatMap 모두 Optional 인스턴스를 반환한다. 다만 map은 람다식이 반환하는 내용물을 Optional 인스턴스로   감싸는 일을 알아서 해주지만, flatMap은 알아서 해 주지 않기 때문에 이 과정을 람다식이 포함하고 있어야 한다. 

 

그렇다면 flatMap은 언제 유용하게 사용할 수 있을까? 

import java.util.Optional;

public class ConInfo {
	
	Optional<String> phone; //null 일 수 있음
	Optional<String> adrs; //null 일 수 있음
	
	public ConInfo(Optional<String> phone, Optional<String> adrs) {
		this.phone = phone;
		this.adrs = adrs;
	}

	public Optional<String> getPhone() {
		return phone;
	}

	public Optional<String> getAdrs() {
		return adrs;
	}
}

 

map보다는 flatMap이 더 어울린다.

String phone = ci.flatMap(c -> c.getPhone()).orElse("There is no phone number");

 

Optional로 감싸서 반환하는 map 메소드의 특성상 다음과 같이 get 메소드 호출을 통해서 커내는 과정을 거쳐야 하기 때문이다.

String phone = ci.map(c -> c.getPhone()).get().orElse("There is no phone number");

 

 

  • Optional 클래스와 성격 및 내용이 유사한 Optional 친구들을 소개  
  • OptionalInt, OptionalLong, OptionbalDouble
import java.util.OptionalInt;

public class OptionalIntBase {
	
	public static void main(String[] args) {
		OptionalInt oi1 = OptionalInt.of(3); //Optional<Integer> 대신해서 쓸 수 있다
		OptionalInt oi2 = OptionalInt.empty();
		
		System.out.print("[Step 1.] : ");
		oi1.ifPresent(i -> System.out.print(i + "\t"));
		oi2.ifPresent(i -> System.out.print(i));
		System.out.println();
		
		System.out.print("[Step 2.] : ");
		System.out.print(oi1.orElse(100) + "\t");
		System.out.print(oi2.orElse(100) + "\t");
		System.out.println();
	}

}