Optional이란?
Optional은 오직 하나의 값을 가지고 있을 수도 없을 수도 있는 컨테이너라고 할 수 있습니다.
Java 8 이전에는 제대로 된 값을 리턴할 수 없을 때 할 수 있는 방법은 두 가지 밖에 없었습니다. 단순히 null
을 반환하거나 Exception를 반환하는 것이 전부였습니다. 이 때 null을 반환하는 경우에는 이를 참조하는 코드에서 또 다시 그 값이 null일 수도 있음에 유의해야 한다는 점에서 좋은 코드가 아니며, 에러(Exception)을 발생시키는 방법은 Stack Track을 출력하는 데에 새로운 리소스가 쓰여야 하므로 이 또한 좋은 방법은 아닙니다.
Java 8부터는 Optional
을 사용해 참조하는 코드에게 명시적으로 빈 값일 수 있음을 알려주고, 빈 값의 경우에 대한 처리를 강제할 수 있게 되었습니다.
이 글은 아래와 같은 User 클래스가 Team 타입의 객체를 필드로 가지도록 정의된 것을 전제로 하고 있습니다.
import java.util.Optional;
public class User {
private Long id;
private String name;
private Team team;
public User(Long id, String name, Team team) {
this.id = id;
this.name = name;
this.team = team;
}
public Optional<Team> getTeam() {
return Optional.ofNullable(team);
}
}
주의할 점
- 리턴 값으로만 쓰기를 권장한다. (메서드 매개변수 타입,
Map
의 키 타입, 인스턴스 필드 타입으로 사용하지 않기를 권고)- Cf. Map의 특징 중 가장 중요한 점은 key는
null
이 될 수 없다는 점이다.
- Cf. Map의 특징 중 가장 중요한 점은 key는
Optional<T>
타입을 반환하는 경우에서null
을 반환하지 말아야 한다. 즉, 해당 값이null
일 지라도null
이 아닌Optional.empty()
를 반환해야 한다.- 이를 참조하는 곳에서
NullPointerException
이 발생하기 때문이다.
- 이를 참조하는 곳에서
- Contianer 성격의 타입들을 Optional로 다시금 감싸지 않아야 한다. -
Collection
,Map
,Stream
,Array
,Optional
을Optional
로 감싸지 말아야
한다.- 이 들은 비어있음을 표현할 수 있는 기능이 있다. 이 기능을 사용해야 한다.
주요 메서드
Optional.of(), Optional.ofNullable(), Optional.empty()
Optional
을 만듭니다.
isPresent(), isEmpty()
Optional
에 값이 있는지 없는지 확인하는 메서드로, isEmpty()
는 Java 11부터 제공합니다.
get()
Optional
에 있는 값을 가져옵니다.
비어있는 경우에는 NoSuchElementException
이 발생합니다.
ifPresent(Consumer)
Optional
에 값이 있는 경우에는 그 값을 인자로 받아 수행할 내용(Consumer
형태)가 들어갑니다.
import java.util.Optional;
import java.util.List;
import java.util.ArrayList;
public class OptionalTest {
public static void main(String[] args) {
List<User> users = new ArrayList<>();
users.add(new User(1, "James", true));
users.add(new User(5, "Carol", false));
Optional<User> optional = users.stream()
.filter(us -> us.getName().startsWith("J"))
.findFirst();
// 값이 존재할 때만 처리
optional.ifPresent(o -> System.out.println(o.getName()));
// 값이 존재하면 가져오고, 없으면 다른 방식으로 처리
User user = optional.orElse(createNewUser());
System.out.println(user.getName()); // "Kim"
}
private static User createNewUser() {
return new User(10, "Kim", false);
}
}
orElse(T)
Optional
에 값이 있으면 가져오고 없는 경우에는 다른 타입(T
)의 값을 반환합니다.
Cf. 인자로 T타입을 반환하는 메서드를 줄 경우 이 메서드는 반드시 한 번은 실행됩니다. 이를 방지하고 싶다면 orElse(T)가 아닌 orElseGet(Supplier)를 사용할 수 있습니다. 따라서, Optional 타입의 객체가 값을 가지지 않을 때 반환할 값으로 전달하려는 값이 상수면 orElse()를, 전달하려는 값이 동적인 값이면 orElseGet()을 사용하는 것이 좋습니다.
orElseGet(Supplier)
Optional
에 값이 있으면 가져오고 없는 경우에 수행할 내용(Supplier
형태)가 들어갑니다.
orElseThrow(), orElseThrow(Supplier)
Optional
에 값이 있으면 가져오고 없는 경우에는 에러를 발생시킵니다.
filter(Predicate)
Optional
에 있는 값을 걸러냅니다.
map(Function), flatMap(Function)
Optional
에 있는 값을 변환합니다.
Cf. flatMap : Optional
안에 있는 인스턴스가 Optional
인 경우에 사용하면 편리하다. 아래 예시 코드의 1번과 2번은 같은 결과를 반환합니다.
public class OptionalTest {
public static void main(String[] args) {
Optional<User> optional = users.stream()
.filter(us -> oc.getName().startsWith("J"))
.findFirst();
// (1) 두번 꺼내는 경우
Optional<Optional<Team>> teamOptional = optional.map(User::getTeam);
Optional<Team> result1 = teamOptional.orElse(Optional.empty());
System.out.println("map result - isPresent? " + result1.isPresent());
// (2) flatMap을 사용해 한번만 꺼내는 경우
Optional<Team> result2 = optional.flatMap(User::getTeam);
System.out.println("flatMap result - isPresent? " + result2.isPresent());
}
}
Cf. Stream
에서 제공하는 flatMap()
과 Optional
에서 제공하는 flatMap()
과는 매우 다릅니다.Stream
에서 제공하는 map()
은 1:1 맵핑입니다. 이와 달리 flatMap()
은 List에 담긴 List의 요소들을 꺼낼 때 주로 쓰입니다. 즉, input은 하나지만 output이 다수일 때 사용됩니다.
'Backend > Java' 카테고리의 다른 글
Java8) Java 비동기 프로그래밍과 CompletableFuture (0) | 2022.10.31 |
---|---|
Java8) Java에서 날짜 및 시간을 나타내는 방식 - Date, Time (0) | 2022.10.31 |
Java8) 함수형 인터페이스(Functional Interface)와 람다(Lambda) (0) | 2022.09.18 |
Java) 2진수의 정수를 10진수 정수로 변환하기(정수의 기수 변환) (0) | 2022.06.30 |
Java) Integer List to int Array (0) | 2022.06.30 |