손영배 블로그 누구나 쉽게 이해하고 습득하기
Optional 본문
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();
}
}
'Java' 카테고리의 다른 글
Java Collection sort 쓰는 방법 (0) | 2019.10.18 |
---|---|
Java 접근제한자 정리 (0) | 2019.05.12 |
Java 배열copy System.arraycopy() (0) | 2019.04.23 |
참조변수 비교연산은 주소값끼리의 비교다, NullPointerException,String (0) | 2019.04.23 |
자바 메모리 사용 영역 (0) | 2019.04.23 |