Optional 클래스

gustjtmd·2022년 1월 15일
0

Java

목록 보기
34/40

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

Optional 클래스는 java.util 패키지로 묶여있으며 다음과 같이 정의되어 있다.
public final class Optional<T> extends Object{
	private final T value; //이 참조 변수를 통해 저장을 한다.
}

Optional은 멤버 value에 인스턴스를 저장하는 일종의 래퍼 클래스이다.

예제1

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")));

        if (os1.isPresent())
            System.out.println(os1.get());
        if(os2.isPresent())
            System.out.println(os2.get());
    }
}
-------------------------------------------------------------------------
Toy1
Toy2

---------------------------------------------------------------------
Optional.of(new String("Toy1")); -> null 인자 전달 불가능.
Optional.ofNullable((new String("Toy2"))); -> null 인자 전달 가능

의 차이점은 null의 허용 여부에 있다.

if(os1.isPresent())	-> 내용물 존재하면 isPresent는 true를 반환
System.out.println(os1.get()) -> get을 통한 내용물 반환


예제2
public 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);
    }
}
-------------------------------------------------------------------------
Toy1
Toy2


위 예제에서 호출하고 있는 메소드 iFPresent의 매개변수 형은 Consumer이다.

public void ifPresent(Consumer<? super T> consumer)

따라서 다음 메소드 accept의 구현에 해당하는 람다식을 ifPresent 호출시 인자로
전달해야 한다.

Consumer<T>	void accept(T t)
-> Optional<String>TString이므로 void accept(String t)

그러면 ifPresent가 호출 되었을때, Optional 인스턴스가 저장하고 있는 내용물이 있으면
이 내용물이 인자로 전달되면서 accept 메소드가 호출된다(람다식이 실행된다)
반면 내용물이 없으면 아무일도 일어나지 않는다. 

따라서 다음의 if문을 
if(os1.isPresent())
	System.out.println(os1.get()); 을 다음과 같이 줄일수 있다.
os1.ifPresent(s -> System.out.println(s));

Optional 클래스를 사용하여 if~else문을 대신하기 : map 메소드의 소개

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());
    }
}
----------------------------------------------------------------------
OPTIONAL STRING
optional_string

위 예제에서 호출한 map 메소드의 매개변수 형은 다음과 같이 Funcion이다. 그리고 map은
제네릭 클래스에 정의된 제네릭 메소드임을 알 수 있다.
(T는 제네릭 클래스의 멤버임을 알려주고 U는 제네릭 메소드임을 알려준다.)

public <U> Optional<U> map(Funcion<? super T, ? extends U> mapper)

따라서 다음 메소드 apply의 구현에 해당하는 람다식을 map 호출시 인자로 전달해야 한다.

Funciont<T, U> 		U apply<T t)

그런데 예제에서는 Optional<String>의 인스턴스를 대상으로 map 메소드를 호출하므로,
메소드의 구현에 대한 람다식을 작성하면 된다.Optional<String> 인스턴스 생성시
String으로 이미 결정이 난 상태이다.

U apply(String t)

그리고 map 메소드는<U>에 대한 제네릭 메소드이므로 이 U는 메소드 호출하는 시점에서 결정이
된다. 그렇다면 map 메소드가 하는 일은 무엇일까?

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

예를 들어서 위 예제의 다음 문장을 보자.

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

위 문장의 람다식은 String 인스턴스의 참조 값을 반환한다. 따라서 위 문장의 map이 
호출되는 순산 반한형 UString으로 결정되어 람다식은 다음 apply 메소드의 몸체를 
구성하게 된다. 그리고 위 문장의 map이 호출되면 아래의 apply메소드의 인자로는 참조변수
os1이 지니는 인스턴스가 전달이 된다.

String apply(String s){
	return s.toUpperCase();	//문자열의 모든 문자를 대문자로 바꿔서 전달
}
그리고 위 메소드가 호출되었을때 반환하는 값을 map은 그냥 반환하지 않고 
Optional 인스턴스로 감싸서 반환한다.

Optional 클래스를 사용하여 if~else문을 대신하기 : orElse 메소드의 소개

Optional 클래스에는 Optional 인스턴스에 저장된 내용물을 반환하는 get 메소드가 존재한다.
그리고 이와 유사한 기능의 orElse 메소드도 존재한다. 
즉 orElse 메소드도 Optional 인스턴스에 저장된 내용물을 반환한다. 
단 저장된 내용물이 없을때 대신해서 반환할 대상을 지정할수 있다는 점에서 get메소드와 차이점.

----------------------------------------------------------------------
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");

        String s2 = os2.map(s -> s.toString())
                        .orElse("Empty");

        System.out.println(s1);
        System.out.println(s2);
    }
}
---------------------------------------------------------------------
Empty
So basic

다음과 같이 empty 메소드를 호출하면 저장하고 있는 내용물이 없는 빈 Optional 인스턴스가
생성된다.

Optional<String> os1 = Optional.empty();
-> Optional<String> os1 = Optional.ofNullable(null);

그리고 이 참조변수를 대상으로 다음 문장을 실행하면 map 메소드가 호출되고, 이어서 
orElse 메소드가 호출되어 그 반환 값이 s1에 저장된다
String s1 = os1.map(s -> s.toString()).orElse("Empty");

그런데 os1이 참조하는 Optional 인스턴스는 비어있다. 이러한경우 map은 빈 Optional
인스턴스를 생성하여 반환한다. 결국 map이 반환한 빈 Optional인스턴스를 대상으로 
orElse 메소드를 호출하게 된다. 그리고 이렇듯 빈 Optional 인스턴스를 대상으로 orElse
메소드를 호출하면 orElse를 호출하면서 전달된 인스턴스가 대신 반환된다.

즉 위의 문장이 실행되면 s1은 문자열 "Empty"를 참조하게 된다.

Optional 클래스를 이용해서 개선해보기 (1)

개선전.
------------------------------------------------------------------------
class ContInfo{
    String phone;   //null일수 있음
    String adrs;    //null일수 있음

    public ContInfo(String ph, String ad){
        phone = ph;
        adrs = ad;
    }
    public String getPhone(){return phone;}
    public String getAdrs(){return adrs;}
}
public class IfElseOptional {
    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);
    }
}
-----------------------------------------------------------------------
There is no phone number
Republic of Korea

개선후
-----------------------------------------------------------------------
Optional<ContInfo> ci = Optional.of(new ContInfo(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 클래스를 이용해서 개선해보기 (2)

개선전.

class Friend{   //친구 정보
    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, ContInfo ci){
        cName = cn;
        cInfo = ci;
    }
    public String getCName(){return cName;}
    public ContInfo getCInfo(){return cInfo;}
}

class ContInfo{ //'회사 정보'에 속하는 '회사 연락처'
    String phone;   //null 일수 있음.
    String adrs;    //null 일수 있음.

    public ContInfo(String ph, String ad){
        phone = ph;
        adrs = ad;
    }
    public String getPhone(){return phone;}
    public String getAdrs(){return adrs;}
}
public class NullPointerCaseStudy {
    public static void showCompAddr(Friend f){  //친구가 다니는 회사 주소 출력
        String addr = null;

        if(f != null){
            Company com = f.getCmp();
            if(com != null){
                ContInfo info = com.getCInfo();
                if(info != null)
                    addr = info.getAdrs();
            }
        }
        if(addr != null)
            System.out.println(addr);
        else
            System.out.println("There's no address information.");
    }
    public static void main(String[] args) {
        ContInfo ci = new ContInfo("321-444-577", "Republic of Korea");
        Company cp = new Company("Yaho co., Ltd.",ci);
        Friend frn = new Friend("Lee Su", cp);
        showCompAddr(frn);
    }
}
--------------------------------------------------------------------------
Republic of Korea


-----------------------------------------------------------------------
개선후.

    public static void showCompAddr(Optional<Friend> f){  //친구가 다니는 회사 주소 출력
        String addr = f.map(Friend::getCmp)
                        .map(Company::getCInfo)
                        .map(ContInfo::getAdrs)
                        .orElse("There's no address information");

        System.out.println(addr);
    }
    public static void main(String[] args) {
        ContInfo ci = new ContInfo("321-444-577", "Republic of Korea");
        Company cp = new Company("Yaho co., Ltd.",ci);
        Friend frn = new Friend("Lee Su", cp);
        showCompAddr(Optional.of(frn));  //친구가 다니는 회사의 주소 출력
    }
}

Optional 클래스의 flatMap 메소드

Optional 클래스를 코드 전반에 사용하기 위해서는 map 메소드와 성격이 유사한 flatMap
메소드를 알아야한다. 

public class OptionalFlatMap {
    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()));
        System.out.println(os3.get());
    }
}
----------------------------------------------------------------------
OPTIONAL STRING
optional string

Optional<String> os2 = os1.map(s -> s.toUpperCase());
Optional<String> os3 = os1.flatMap(s -> Optional.of(s.toLowerCase()));

두 메소드의 차이점.
map은 람다식이 반환하는 내용물을 Optional 인스턴스로 감싸는 일을 알아서 해주지만
flatMap은 알아서 해주지 않기 때문에 이 과정을 람다식이 포함하고 있어야한다.
flatMap을 유용하게 사용하기.

class ContInfo{
    Optional<String> phone; //null일수 있음.
    Optional<String> adrs;  //null일수 있음.

    public ContInfo(Optional<String> ph, Optional<String> ad){
        phone = ph;
        adrs = ad;
    }
    public Optional<String> getPhone() {return phone;}
    public Optional<String> getAdrs(){return adrs;}
}
public class FlatMapElseOptional {
    public static void main(String[] args) {
        Optional<ContInfo> ci = Optional.of
                (new ContInfo(Optional.ofNullable(null),Optional.of("Republic of Korea")));

        String phone = ci.flatMap(ContInfo9::getPhone).orElse("There is no phone number.");
        String addr = ci.flatMap(c -> c.getAdrs()).orElse("There is no address.");
        System.out.println(phone);
        System.out.println(addr);
    }
}
---------------------------------------------------------------------
There is no phone number.
Republic of Korea

class ContInfo{
	Optional<String> phone; //null일수 있음.
    	Optional<String> adrs; //null일수 있음.
}

위의 클래스와 같이 멤버를 Optional로 두면 이 멤버와 관련된 코드 전반에 걸쳐서 
코드의 개선을 기대할수있다. 
그런데 이렇게 멤버를 Optional로 두는 경우에는 map보다 flatMap이 더 어울린다.
만약에 예제의 다음 문장을 map 메소드를 호출하는 형태로 작성한다면.

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

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

Optional과 OptionalXXX와의 차이점.

OptionalInt, OptonalLong, OptionalDouble
OptionalXXX 클래스들은 Optional 클래스들보다 그 기능이 제한적이다. 
그래서 Optional을 대신 하는 경우는 많지 않다 

과도한 언박싱과 오토박싱을 줄이기 위해서 사용되는 클래스들이다.
profile
반갑습니다

0개의 댓글