| Optional<T> 클래스란?
- 자바에서 모든 객체는 Referece Type으로 nullable하다. * null이 발생할 수 있는 타입
> Referece Type의 객체는 null을 발생시킬 수 있다. -- NullPointerException
- Optional<T>은 객체를 nullable하게 쓸 수 있도록 하는 Wrapper class로, 객체 사용 시 null을 명시적으로 처리하게 한다.
* 참고로 코틀린의 경우, nullable한 타입을 구분하는 코드가 별도로 존재한다.
Optional은 주로 "결과 없음"을 명확하게 나타내야 하고,
null을 사용하면 오류가 발생할 수 있는 메소드 반환 유형으로 사용된다.
Optional 변수는 null일 수 없으며 항상 Optional 인스턴스를 가리켜야한다. -- Oracle Docs
- 장점 : null을 직접 핸들링하지 않음, null 여부를 타입만으로 나타낼 수 있음, 체이닝을 통한 중간 및 종단 처리
Optional<T> | Optional.empty() | |
T | null |
- Optional의 생성자를 보면, 아래와 같이 Objects.requireNonNull()을 통해
감싸려는 객체 데이터가 null일 때, NullPointException을 발생시키도록 한다.
- 따라서 Optional로 감싼다고 하여도 NullPointException이 발생하지 않는 것은 아니다.
private Optional() {
this.value = null;
}
private Optional(T value) {
this.value = Objects.requireNonNull(value); // null이면 NullPointException 발생
}
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
| Optional 객체의 맴버 변수와 생성자
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
private Optional() {
this.value = null;
}
| Optional 객체의 메소드
isPresent(): boolean | null인지 아닌지 여부를 boolean으로 반환 |
of() | 데이터를 Optional<T>로 감싼다. |
ofNullable() | of()와 동일하나 null이 발생할 때 처리 **null 발생 시 꼭 이 메소드 사용 |
get() | null이 아닌 경우, 해당값 반환 **null일 때 예외 발생시키므로, 사용 시 주의요함 |
orElse(<T>) | null일 경우, 특정 데이터를 반환 |
orElseGet(() -> getMethod()) | null일 경우, 함수형 메소드 실행 |
orElseThrow(()->new Exception()) | null일 경우, 지정한 예외 발생 |
filter() | 조건식에 따라 필터링 |
map() | 조건식에 따라 데이터를 매핑 |
flatMap() | map과 동일하나 null처리가 다름 |
(1) of()와 ofNullable()의 차이
- ofNullable()의 경우 null이 발생할 때 empty() 메소드를 사용하는데, empty() 메소드를 쓰면 Empty 맴버 변수를 반환한다.
private static final Optional<?> EMPTY = new Optional<>();
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
(2) isPresent(), ifPresent()
public boolean isPresent() {
return value != null;
}
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
(3) get(), orElse(), orElseGet(), orElseThrow()
- get()은 사용을 지양하는 편이 좋다고 한다.* 감싼 데이터값이 null일 때, NoSuchElementException이 발생
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
- orElse()과 orElseGet()의 차이점은 인자로 함수(ex. getRandomNumber())를 전달할 때
> orElse()의 경우 null이 발생하지 않아도 함수를 실행한 후 결과값을 orElse(//결과값//)에 전달하는 한 편,
> orElseGet()을 쓰면 Supplier라고 하는 함수형 인터페이스를 통해
null이 발생할 때에만 함수를 실행한뒤 결과값을 전달한다.
// 가정: optionalUser.findById(userId)는 null X
// getRandomUser() 실행 --> 결과값이 orElse()안에 담김
optionalUser.findById(userId).orElse(getRandomUser());
// getRandomUser() 실행 X
optionalUser.findById(userId).orElseGet(() -> getRandomUser());
- orElseThrow()는 null 발생 시, 지정한 Exception을 반환하게 하는 메소드이다.
(4) filter(), map(), flatMap()
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
| 언제쓸까? - 값이 없을 수도 있을 때
(1) DB를 조회할 때
// JPA을 통해 Optional<T> 타입의 반환값이 null일 경우 처리하는 법
Optional<User> user = userRepository.findById(userId)
.orElseThrow(() -> new Exception());
String userName = userRepository.findUserNameById(userId)
.map(user -> user.getUserName())
.orElse("이름 없음");
(2) 조회한 객체에 특정 칼럼이 비어있을 때
@Entity(name = "user")
public class User{
@Id
@GeneratedValue
private Long id;
private String userId;
private String password;
private String thumbnail;
public Optional<String> getThumbnail(){
return Optional.ofNuallable(thumbnail);
}
}
(3) 기존 시스템의 리턴 값이 null을 리턴할 때
Optional.ofNuallable(userRepository.findByName(userName))
.ifPresent(user -> list.add(user));
(4) 필드에 Optional은 지양하기
- Optional은 함수 반환을 목적으로 만들어졌다.
- Serializable을 구현하지 않아 직렬화를 하면 이상한 값이 나온다.
[직렬화란] 메모리는 가상 메모리와 물리 메모리로 나뉘어져 있다. 가상 주소는 논리적인 주소일 뿐이지, 실제 저장되는 위치는 다를 수 있다. 객체를 그대로 저장하게 되면, 가상 주소까지 저장되게 되는데, 메모리 주소는 다른 시스템에 전달해도 의미가 없다. 따라서 불필요한 정보는 제외하고 타입 정보, 값 정보를 Byte형태로 만들어 보내는데 이를 직렬화라 한다. 직렬화를 하려면 클래스가 serializable를 구현해야한다. |
- Optional.empty()를 빠뜨릴 확률이 높다. (휴먼 에러)
| Optional을 제대로 쓰기
(1) 비어있을 컬렉션을 표현할 땐 Collections.emptyList()를 사용한다.
Optional<List<User>> findAll(); // !!사용하지 말아야 한다.
Collections.emptyList(); // Collections의 emptyList()를 사용한다.
- 컬렉션 요소에 Optional은 절대 금지
// 사용자가 Optional을 매번 판단해야하는 상황발생
List<Optional<User>> findAll(); // !!사용하지 말아야 한다.
// ----사용시
for (Optional<User> opt : list) {
// 매번 optional을 객체로 변환
User user = opt.get(); // 불필요하다.
}
// User타입 그대로 반환
List<User> findAll();
(2) Optional을 파라미터로 넘기지 않는다.
(3) Optional 값을 가져올 때 .get()은 되도록 지양한다.
- get()을 쓰면, null일 때, NoSuchElementException을 발생시킨다.
- optional의 중단, 종단 메소드를 체이닝하여 사용하는게 낫다.
(4) Optional의 중간, 종단 메소드를 쓰기 위해 불필요한 옵셔널을 하지 않는다.
[ 참고 및 출처 ]
부트 캠프 수업을 들은 후 정리
https://tecoble.techcourse.co.kr/post/2021-06-20-optional-vs-null/
http://www.tcpschool.com/java/java_stream_optional
orElse()와 orElseGet()의 차이 : https://kdhyo98.tistory.com/40
'Language > Java' 카테고리의 다른 글
[이펙티브 자바] 객체의 생성 (0) | 2022.09.29 |
---|---|
JAVA 유용한 타입 - Enum (0) | 2022.09.15 |
SOLID 원칙 (0) | 2022.08.29 |
JAVA 라이브러리 - 컬렉션 (0) | 2022.08.11 |
JAVA 라이브러리 - 제네릭 클래스 (0) | 2022.08.10 |