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>의 T가 String이므로 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이
호출되는 순산 반한형 U가 String으로 결정되어 람다식은 다음 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을 대신 하는 경우는 많지 않다
과도한 언박싱과 오토박싱을 줄이기 위해서 사용되는 클래스들이다.
Reference
この問題について(Optionalクラス), 我々は、より多くの情報をここで見つけました https://velog.io/@gustjtmd/Optional-클래스テキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol