Optional 클래스
코드가 방대해지면 if else문이 좀 거슬리게 된다.
왜냐하면 분기가 계속해서 나뉘어지기 때문이다.
그래서 사용하는 것이 Optional 클래스라고 일단 알아두자.
일단 NullPointerException 예외의 발생 상황을 한번 보자.
class Freind { // 친구 정보
String name;
Company cmp; // null 일 수 있음
public Friend(String n, Company c) {
name = n;
cmp = c;
}
public String getName() {return name;}
public Company getCmp() {return cmp;}
}
class Company { // 친구 정보 에 속하는 회사 정보
String cName;
ContInfo cInfo; // null 일 수 있음
public Company(String cn, ConInfo ci){
cNmae = cn;
cInfo = ci;
}
public String getCName(){return cName;}
}
가장 좋은 것은 null로 초기화할 인스턴스 변수가 없는 것이나 허용해야 하는 경우도 있다.
NullPointerException 예외의 발생 상황 2
public static void showCompAddr(Friend f) {
String addr = null;
if (f != null) { // 인자가 전달된 것이 null일 수도 있으니
Company com = f.getCmp();
if(com != null) { // 회사 정보가 없을 수도 있으니
addr = info.getAdrs();
}
if (info != null)
...
}
null 가능성에 대비하여 코드를 이런 식으로 작성할 수도 있으나
이 역시 번거롭고 코드 스타일도 만족스러운 편은 아니다.
이를 해결하기 위해 등장한 것이 Optional 클래스이다.
Optional 클래스의 기본적인 사용 방법
public final class Optional<T> extends Object {
private final T value; // 이 참조변수를 통해 저장을 하는 일종의 래퍼 클래스
}
public staitc void main(String[] args) {
Optional<String> os1 = Optional.of(new String("Toy1")); // of 는 null을 허용하지 않음
Optional<String> os2 = Optional.ofNullable(new String("Toy2")); // ofNullable은 null 허용
if(os1.isPresent()) System.out.println(os1.get());
if(os2.isPresent()) System.out.println(os2.get());
}
class StringOptional2 {
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 인터페이스형 인스턴스를 요구한다.
즉, 그 안의 void accept(T t)를 요구한다.
결국 ifPresent 메소드는 만약 내용물이 존재하면 인자로 전달되는 인스턴스의 accept메소드를 실행한다.
결국 람다식, 메소드 참조를 생각하면, 그 람다식, 메소드를 실행한다.
Optional 클래스를 이용한 if else문 대체
ex.
public static void main(String[] args) {
ContInfo ci = new COntInfo(null, "Republic of Korea");
String phone;
String addr;
if(ci.phone != null) phone = ci.getPhone();
else phone = "There is no phone number."
if(ci.adrs != null) addr = ci.getAdrs();
else addr = "There is no address.";
System.out.println(phone);
System.out.println(addr);
}
이를 어떻게 대체할 수 있을까?
map메소드
Optional 클래스에 정의되어 있는 메소드이다.
파이썬의 map과는 다르다.
map메소드는 apply 메소드가 반환하는 대상을 Optional 인스턴스에 담아서 반환한다.
orElse 메소드
Optional 인스턴스를 대상으로 orElse 메소드를 호출하면 orElse를 호출하면서 전달된 인스턴스가 대신 반환된다.
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");
String s2 = os2.map(s -> s.toString()).orElse("Empty");
System.out.println(s1);
System.out.println(s2);
}
그럼 위에서 본 예시를 다음과 같이 개선할 수 있다.
public static void main(String[] args) {
Optional<ContInfo> ci = Optional.or(new COntInfo(null, "Republic of Korea"));
String phone;
String addr;
phone = ci.map(c -> c.getPhone()).orElse("There is no phone number.");
addr = ci.map(c -> c.getAddr()).orElse("There is no address");
System.out.println(phone);
System.out.println(addr);
}
이 때 phone 에는 map메소드의 호출결과 null을 감싼 Optional 객체가 리턴이 되고, 그다음 orElse 메소드가 호출되는데
null로 감싼 Optional 객체인 경우 orElse 내부의 메소드의 리턴값을 감싼 Optional 객체가 호출되므로
phone 변수에는 "There is no phone number" 가 저장된다.
flatMap 메소드
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.flatMap(s -> Optional.of(s.toLowerCase()));
Sytem.out.println(os3.get());
}
map은 람다식이 반환하는 내용물을 Optional로 감싸서 반환!
flatMap은 그냥 반환!! 따라서 필요하면 직접 Optional 로 감싸야 한다.
코드 전반에서 Optional 클래스 사용하기
ex.
class ContInfo {
Optional<String> phone; // null 일 수 있음
Optional<String> adrs; // null 일 수 있음
public ContInfo(Optional<String> ph, Optional<String> ad) {
phone = ph;
adrs = ad; // 오토 박싱될 것
}
public String getPhone() {
String s = phone.orElse("There is no Phone Number");
return s;
}
public Strins getAdrs() {
String s = adrs.orElse("There is no address");
return s;
}
}