Optional 클래스
- Optional<T>클래스는 T타입의 객체를 포장해주는 일종의 래퍼 클래스이다.
- Optional 클래스는 if~else 구문을 대신할 수 있다.
- 조건에 따른 코드의 흐름이 나눠지는 것을 막을 수 있어 코드를 더 깔끔하게 구성할 수 있다.
- Optional 객체를 사용하면 예상치 못한 NullPointerException 예외를 단순하게 처리할 수 있다. 즉, 복잡한 조건문 없이도 null값으로 인해 발생하는 예외를 처리할 수 있게 된다.
💡 NullpointerException 예외 처리
- 클래스를 정의할 때 인스턴스 변수는 null이 되지 않게 유효한 값을 가지고 있도록 초기화 해주는 것이 좋지만, null을 허용해야 하는 경우도 있다.
- 이때 null 가능성에 대비하는 코드의 작성은 번거롭고 코드도 복잡하다. 이를 해결하기 위해 자바 8에서 Optional 클래스가 등장하게 되었다.
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; // 회사 이름
CompInfo cInfo; // 회사 정보 (null일수 있음)
public Company(String cn, CompInfo ci) {
cName = cn;
cInfo = ci;
}
public String getCName() { return cName; }
public CompInfo getCInfo() { return cInfo; }
}
class CompInfo { // 회사 정보에 속하는 회사 연락처
String phone; // 회사 번호 (null일수 있음)
String adrs; // 회사 주소 (null일수 있음)
public CompInfo(String ph, String ad) {
phone = ph;
adrs = ad;
}
public String getPhone() { return phone; }
public String getAdrs() { return adrs; }
}
class Optional_NullPointerCase {
public static void showCompAddr(Friend f) { // 친구가 다니는 회사 주소 출력
String addr = null;
if (f != null) { // 인자로 전달된 것이 null 일 수도 있으니
Company com = f.getCmp();
if (com != null) { // 회사 정보가 없을 수도 있으니
CompInfo info = com.getCInfo();
if (info != null) // 회사 연락처 정보가 없을 수도 있으니
addr = info.getAdrs();
}
}
if (addr != null) // 주소 정보를 얻지 못할수도 있으니
System.out.println(addr);
else
System.out.println("회사 주소 정보가 없습니다.");
}
public static void main(String[] args) {
CompInfo ci = new CompInfo("321-444-577", "Republic of Korea");
Company cp = new Company("자취 컴퍼니", ci);
Friend frn = new Friend("wuga", cp);
showCompAddr(frn); // 친구가 다니는 회사의 주소 출력
}
}
Republic of Korea
💡 if ~ else문 대신하기
// 기존 코드
class CompInfo { // 회사 정보
String phone; // null일 수 있음
String adrs; // null일 수 있음
public CompInfo(String ph, String ad) {
phone = ph;
adrs = ad;
}
public String getPhone() { return phone; }
public String getAdrs() { return adrs; }
}
class Optional_IfElse {
public static void main(String[] args) {
CompInfo ci = new CompInfo(null, "Republic of Korea");
String phone;
String addr;
if (ci.phone != null)
phone = ci.getPhone();
else
phone = "전화번호 정보가 없습니다.";
if (ci.adrs != null)
addr = ci.getAdrs();
else
addr = "주소 정보가 없습니다.";
System.out.println(phone); // 전화번호 정보가 없습니다.
System.out.println(addr); // Republic of Korea
}
}
// 개선 후 코드
class CompInfo { // 회사 정보
String phone; // null일 수 있음
String adrs; // null일 수 있음
public CompInfo(String ph, String ad) {
phone = ph;
adrs = ad;
}
public String getPhone() { return phone; }
public String getAdrs() { return adrs; }
}
class Optional_IfElse_MapOrElse {
public static void main(String[] args) {
Optional<CompInfo> ci = Optional.of(new CompInfo(null, "Republic of Korea"));
String phone = ci.map(c -> c.getPhone()).orElse("전화번호 정보가 없습니다.");
String addr = ci.map(c -> c.getAdrs()).orElse("주소 정보가 없습니다.");
System.out.println(phone); // 전화번호 정보가 없습니다.
System.out.println(addr); // Republic of Korea
}
}
Optional 클래스의 사용 방법
Optional 객체 생성
- of() 메소드나 ofNullable() 메소드를 사용해 Optional 객체를 생성할 수 있다.
메소드 | 설명 |
of() | • null이 아닌 명시된 값을 가지는 Optional 객체를 반환 • of 메소드를 통해 생성된 Optional 객체에 null이 저장되면 NullPointerException 예외 발생 |
ofNullable() | • 명시된 값이 null이 아니면 명시된 값을 가지는 Optional 객체를 반환 • 명시된 값이 null이면 비어있는 Optional 객체를 반환 • 따라서 만약 참조 변수의 값이 null이 될 가능성이 있다면 ofNullable 메소드를 사용해 Optional 객체를 생성하는 것이 좋다. |
empty() | • 비어있는 Optional 객체를 반환 |
* java.utill 패키지에 묶여 있음
Optional 객체 접근
- get() 메소드를 호출하기 전에 isPresent() 메소드를 사용해 Optional 객체에 저장된 값이 null인지 아닌지 먼저 확인 후 호출하는 것이 좋다.
메소드 | 설명 |
T get() | • Optional 객체에 저장된 값을 반환 • Optional 객체에 저장된 값이 null이면, NoSuchElementException 예외 발생 |
boolean isPresent() | • Optional 객체에 저장된 값이 존재하면 true, 값이 존재하지 않으면 flase 반환 |
void ifPresent(Consumer<? super T> consumer) | • Optional 객체에 저장된 값이 존재하면, 그 값을 인자로 전달하며 accept 메소드를 호출 • 값이 존재하지 않으면 아무일도 일어나지 않음 |
class Optional_makeInstance {
public static void main(String[] args) {
// String 인스턴스를 저장한 Optional 인스턴스 생성
Optional<String> os1 = Optional.of(new String("Red"));
Optional<String> os2 = Optional.of(new String("blue"));
// Optional 인스턴스에 접근
if(os1.isPresent())
System.out.println(os1.get());
if(os2.isPresent())
System.out.println(os2.get());
os1.ifPresent(s -> System.out.println(s)); //람다식 버전
os2.ifPresent(System.out::println); // 메소드 참조 버전
}
}
Red
blue
Red
blue
위 예제를 보면 ifPresent() 메소드 사용을 통해 if문을 생략할 수 있음을 알 수 있다.
Optional 클래스의 메소드
앞서 Optional 클래스를 사용하여 if~else 구문을 사용하지 않을 수 있다 하였다.
이에 필요한 Optional 클래스의 메소드를 알아보자.
map()
public <U> Optional<U> map(Function<? super T, ? extends U> mapper)
- Function 함수형 인터페이스의 apply(T t) 추상메소드를 구현하는 람다식을 map 호출시 인자로 전달해야 한다.
- map 메소드는 람다식을 적용해 만들어진 결과물을 Optional 인스턴스에 담아서 반환한다.
class Optional_map {
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
flatMap( )
- map은 Optional로 감싸서 반환한다면, flatMap은 감싸지 않고 내용물 자체를 반환한다.
- 필요시 직접 Optional로 감싸줘야 한다.
- flatMap() 메소드는 Optional 인스턴스를 클래스의 멤버로 두는 경우 유용하게 사용할 수 있다.
Optional<String> os1 = Optional.of("Optional String");
Optional<String> os2 = os1.flatMap(s -> Optional.of(s.toLowerCase())); // optional string
더보기
class CompInfo { // 회사 정보
Optional<String> phone; // null일 수 있음
Optional<String> adrs; // null일 수 있음
public CompInfo(Optional<String> ph, Optional<String> ad) {
phone = ph;
adrs = ad;
}
public Optional<String> getPhone() { return phone; }
public Optional<String> getAdrs() { return adrs; }
}
class Optional_FlatMapElse {
public static void main(String[] args) {
Optional<CompInfo> ci = Optional.of(new CompInfo(Optional.ofNullable(null), Optional.of("Republic of Korea")));
String phone = ci.flatMap(c -> c.getPhone()).orElse("전화번호 정보가 없습니다.");
String addr = ci.flatMap(c -> c.getAdrs()).orElse("주소 정보가 없습니다.");
System.out.println(phone);
System.out.println(addr);
}
}
전화번호 정보가 없습니다.
Republic of Korea
orElse()
- Optional 객체에 저장된 내용물이 있으면 내용물을 반환, 반환할 내용물이 없으면 인자로 전달된 인스턴스를 대신 반환한다.
- get()메소드와 동일하게 Optional 객체에 저장된 내용물을 반환하지만, 반환할 대상을 지정해줄 수 있다는 점에서 차이가 있다.
class Optional_orElse {
public static void main(String[] args) {
Optional<String> os1 = Optional.empty(); // 비어있는 Optional 객체 생성
Optional<String> os2 = Optional.of("Hi Optional.");
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
Hi Optional.
기본 타입의 Optional 클래스
자바에서는 기본 자료형을 위한 별도의 Optional 클래스를 제공하고 있다.
이러한 클래스는 반환 타입이 Optional<T> 타입이 아닌 해당 기본 타입이라는 점에서 차이가 있다.
- OptionalInt 클래스
- OptionalLong 클래스
- OptionalDouble 클래스
Optional과 Optional__와의 차이점
- 기본자료형을 대상으로한 Optional__을 사용해 객체를 생성하면 오토 박싱, 오토 언박싱을 생략한 코드를 작성할 수 있다.
- Optional__클래스들은 Optional 클래스보다 기능이 제한적이다.
- Optional__클래스들에는 map과 flatMap 메소드가 정의되어 있지 않다.
더보기
// Optional<T>
class OptionalInt_Base1 {
public static void main(String[] args) {
Optional<Integer> oi1 = Optional.of(3); // 오토 박싱
Optional<Integer> oi2 = Optional.empty();
System.out.print("[step 1] : ");
oi1.ifPresent(i -> System.out.print(i + " "));
oi2.ifPresent(i -> System.out.print(i));
System.out.println();
System.out.print("[step 2] : ");
System.out.print(oi1.orElse(100) + " ");
System.out.print(oi2.orElse(100) + " ");
System.out.println();
}
}
[step 1] : 3
[step 2] : 3 100
// OptionalInt 적용
class OptionalInt_Base2 {
public static void main(String[] args) {
OptionalInt oi1 = OptionalInt.of(3);
OptionalInt oi2 = OptionalInt.empty();
System.out.print("[step 1] : ");
oi1.ifPresent(i -> System.out.print(i + " "));
oi2.ifPresent(i -> System.out.print(i));
System.out.println();
System.out.print("[step 2] : ");
System.out.print(oi1.orElse(100) + " ");
System.out.print(oi2.orElse(100) + " ");
System.out.println();
}
}
[step 1] : 3
[step 2] : 3 100
Reference
http://www.tcpschool.com/java/java_stream_optional
'☕ Java > 이론' 카테고리의 다른 글
[Java] 체크 예외와 언체크 예외 (0) | 2023.03.07 |
---|---|
[Junit5] @ParameterizedTest 사용하기 (0) | 2022.10.11 |
[Java] 추상 클래스 (0) | 2022.08.30 |
[Java] 스트림 (Stream) (0) | 2022.02.08 |
[Java] 메소드 참조 (Method References) (0) | 2022.02.04 |
[Java] 함수형 인터페이스 (0) | 2022.02.03 |
[Java] 람다 (0) | 2022.01.28 |
[Java] 네스티드 클래스, 이너 클래스 (0) | 2022.01.27 |