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 |