안녕하세요
오늘은 Java 문법에 대해서 정리를 해볼 건데요
그중에서 java 8 버전에서 나온
Optional 문법에 대해서 자세히 알아볼게요

Java 8부터 지원하는 Optional 클래스에 대해서 알아보겠습니다.
예를 들어서 Order 클래스는 Member 타입의 member 필드를 가지며, Member 클래스는 다시 Address 타입의 address 필드를 가집니다.
그리고 "어떤 주문을 한 회원의 어느 도시에 살고 있는지 알아내기" 위해서 다음과 같은 메소드가 있다고 가정해보자
public String getCityOfMemberFromOrder(Order order){
return order.getMember().getAddress().getCity();
}
위 메소드가 NullPointerException(NPE) 노출된 상태가 보이시나요?
Java 8 이전의 예제
public String getCityOfMemberFromOrderNotNull(Order order){
if (order != null){
if(order.getMember() != null){
if(order.getMember().getAddress() != null){
if(order.getMember().getAddress().getCity() != null){
return order.getMember().getAddress().getCity();
}
}
}
}
return "Seoul";
}
NullPointerException(NPE) 예외에 위험에 노출된 코드를 다음과 같은 코딩 스타일로 회피하였습니다.
들여쓰기 때문에 알기가 쉽지 않기 때문에 살짝 ? 리펙토링을 해서
public String getCityOfMemberFromOrderNotNullRefactoring(Order order){
if(order == null){
return "Seoul";
}
Member member = order.getMember();
if(member == null){
return "Seoul";
}
Address address = member.getAddress();
if(address == null){
return "Seoul";
}
String city = address.getCity();
if (city == null){
return "Seoul";
}
return city;
}
전반적으로 코드 읽기가 조금 쉬워지긴 했지만, 결과를 여러 고에 리턴하기 때문에 후에 유지 보수하기가 난해해졌습니다.
2가지 방법 모두 기본적으로 객체의 필드나 메소드에 접근하기 전에 null 체크를 함으로써 NPE를 방지하고 있습니다. 하지만 안타깝게도 이로 인해 초기 버전의 메소드보다 코드가 상당히 길어지고 지저분해졌음을 볼 수 있습니다.
java.util.Optional<T> 클래스
null과 관련된 2가지 문제
- 런타임에 NPE(NullPointerException)라는 예외를 발생시킬 수 있습니다.
- NPE 방어를 위해서 들어간 null 체크 로직 때문에 코드 가독성과 유지 보수성이 떨어집니다.
Optional<T> 클래스는 Integer, Double 클래스처럼 'T' 타입의 객체를 포장해 주는 래퍼 클래스(Wrapper Class)입니다.
따라서 Optional 인스턴스는 모든 타입의 참조 변수를 저장할 수 있습니다.
이러한 Optional 객체를 사용하면 예상치 못한 NullPointerException(NPE) 예외를 제공되는 메서드를 간단히 피할 수 있습니다.
즉, if 조건문 없이도 널(null) 값으로 인해 발생하는 예외를 처리할 수 있게 됩니다.
public final class Optional<T> {
// If non-null, the value; if null, indicates no value is present
private final T value;
...
}
출처 : https://mangkyu.tistory.com/70
Optional 사용법
Optional 객체를 생성하기 위해서는 다음 메서드를 사용해야 합니다.
Optional<String> optional = Optional.of(value);
위에 경우 value 변수의 값이 null인 경우 NullPointerException 예외가 발생한다. 반드시 값이 있어야 하는 경우에 of() 메서드를 사용합니다.
Optional<String> optional = Optional.ofNullable(value);
따라서 만약 참조 변수의 값이 만에 하나 null이 될 경우가 있다면, ofNullable() 메서드를 사용하여 Optional 객체를 생성하는 것이 좋습니다.
value 변수가 null인 경우 Optional.empty()가 리턴합니다.
Optional<String> optional = Optional.empty();
빈 Optional 객체를 생성한다. 비어있는 Optional 객체라 하면, Optional 객체 자체는 있지만, 내부에서 가리키는 참조가 없는 경우 빈 객체라고 한다. Optional.empty() 객체는 미리 생성되어 있는 싱글턴 인스턴스입니다.
filter()
import java.util.Optional;
public class OptionalTest {
private static String v;
public static void main(String[] args) {
String s = Optional.of("ABCD").filter(v -> v.startsWith("AB")).orElse("NOT AB");
String s1 = Optional.of("XYZ").filter(v -> v.startsWith("AB")).orElse("NOT AB");
System.out.println("s = " + s);
System.out.println("s1 = " + s1);
}
}

filter 메서드의 인자로 받은 람다식 참이면, Optional 객체를 그대로 통과시키고 거짓이면 Optional.empty()를 반환해서 추가로 처리가 안되도록 한다.
"ABCD"로 시작해서 Optional 객체의 filter() 조건은 참이므로 "ABCD"가 리턴된다. "XYZ"로 시작한 Optional 객체의 경우 filter()에서 빈 Optional 객체라 리턴되므로 orElse()에 적어놓은 "NOT AB"가 리턴됩니다.
map()
public static void main(String[] args) {
String s = Optional.of("ABCD").map(String::toLowerCase).orElse("Not AB");
System.out.println("s = " + s);
}

Optional 객체의 값에 어떤 수정을 가해서 다른 값으로 변경하는 메서드입니다.
OrElse()
OrElse()와 같은 메서드를 이용하면 null 대신에 대체할 값을 지정할 수 있습니다.
- orElse() : 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으며 인수로 전달된 값을 반환
- orElseGet() : 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 람다 표현식의 결괏값을 반환
- orElseThrow() : 저장된 값이 존재하면 그 값을 반환하고, 값이 존재하지 않으면 인수로 전달된 예외를 발생
※ orElse()와 orElseGet() 차이 ※
Optional API의 단말 연산에는 orElse와 orElseGet 함수가 있다. 비슷해 보이는 두 함수는 엄청난 차이가 있다.
● orElse: 파라미터 값을 받는다, 값이 미리 존재하는 경우에 사용한다.
● orElseGet : 파라미터로 함수형 인터페이스(함수)를 받는다. 값이 미리 존재하지 않는 거의 대부분의 경우에 사용하면 된다.
첫번째 예제 보안
public String getCityOfMemberFromOrderOptional(Order order){
return Optional.ofNullable(order)
.map(Order::getMember)
.map(Member::getAddress)
.map(Address::getCity)
.orElse("Seoul");
}
첫 번째 포스팅에서 다루었던 2가지 전통적인 NPE 방어 패턴에 비해 훨씬 간결하고 명확해진 코드를 볼 수 있습니다. 우선 기존에 존재하던 조건문들이 모두 사라지고 Optional의 수려한(fluent) API에 의해서 단순한 메소드 체이닝으로 모두 대체되었습니다.
Optional 위험한 이유, Optional 올바르게 사용해야 하는 이유
Optional을 사용하면 Null-Safe 해지고, 가독성이 좋아지며 애플리케이션이 안정적이 된다는 등과 같은 얘기들을 많이 접할 수 있다. 하지만 이는 Optional을 목적에 맞게 올바르게 사용했을 때의 이야기이고, Optional을 남발하는 코드는 오히려 다음과 같은 부작용을 유발할 수 있다.
- NullPointerException 대신 NoSuchElementException가 발생함
- 이전에는 없었던 새로운 문제들이 발생함
- 코드의 가독성을 떨어짐
- 시간적, 공간적 비용(또는 오베헤드)이 증가함
밑에 설명을 다른 블로그에서 참고 해주세요
반환 타입으로만 사용하라
Optional은 반환 타입으로써 에러가 발생할 수 있는 경우에 결과 없음을 명확히 드러내기 위해 만들어졌으며, Stream API와 결합되어 유연한 체이닝 api를 만들기 위해 탄생한 것이다. 예를 들어 Stream API의 findFirst()나 findAny()로 값을 찾는 경우에 어떤 것을 반환하는 게 합리적 일지 Java 언어를 설계하는 사람이 되어 고민해보자. 언어를 만드는 사람의 입장에서는 Null을 반환하는 것보다 값의 유무를 나타내는 객체를 반환하는 것이 합리적일 것이다. Java 언어 설계자들은 이러한 고민 끝에 Optional을 만든 것이다.
그러므로 Optional이 설계된 목적에 맞게 반환 타입으로만 사용되어야 한다
Optional을 잘못 사용하는 것은 비용은 증가시키는 반면에 코드 품질은 오히려 악화시킨다. 그러므로 위에서 정리한 규칙을 준수하며 올바른 방식으로 Optional을 사용하도록 하자
※참고※
https://mangkyu.tistory.com/70
https://mangkyu.tistory.com/203
https://hbase.tistory.com/212
http://www.tcpschool.com/java/java_stream_optional
https://www.oracle.com/technical-resources/articles/java/java8-optional.html
https://docs.oracle.com/en/java/javase/11/
https://www.daleseo.com/java8-optional-before/
'java' 카테고리의 다른 글
| [JAVA 기초] Wrapper Class 란? (0) | 2023.06.29 |
|---|---|
| [Java] Thread 란? (0) | 2022.07.30 |
| ThreadLocal 이란? (0) | 2022.07.28 |
| [Java] 컬렉션 - Collection 이란? (0) | 2022.07.18 |
| [Java] Static import 란? (0) | 2022.07.14 |