자바 옵셔널은 자바8에서 처음 도입이 되었고 도입된 이유는 null
때문이다. null
은 자바 프로그래머들에게 불편함을 더 많이 제공한다.
class Person {
private String name;
// constructor, getter, setter method 생략
}
class House {
private Person owner;
private String address;
// constructor, getter, setter method 생략
}
위 코드를 보자. Person
클래스는 한 사람을 표현한다. House
는 owner
와 Address
를 표현할 수 있다.
어떤 개발자가 다음과 같은 코드를 구현했다고 하자.
void main() {
// 이 메서드는 자신이 가지고 있는 집들 중 아무거나 반환합니다. null 은 나오지 않는다고 가정
House house = houseService.getRandomHouse();
System.out.println("owner: " + house.getOwner().getName());
System.out.println("address: " + house.getAddress());
}
집 주인과 주소가 있다면 별 문제 없이 잘 실행될 것이다. 근데 만약 주인이 없는 집이 있다면?
void main() {
House house = houseService.getRandomHouse();
System.out.println("onwer: " + house.getOwner().getName()); // java.lang.NullPointerException 발생!
System.out.println("address: " + house.getAddress());
}
콘솔에 노출하려고할 때 NPE
예외가 일어나서 프로그램이 제대로 수행되지 않을 것이다.
void main() {
House house = houseService.getRandomHouse();
if (house.getOnwer() != null) {
System.out.println("onwer: " + house.getOwner().getName());
}
System.out.println("address: " + house.getAddress());
}
null
인 경우를 제외시키기 위해 if
문을 써서 해결할 수 있지만, 새로운 요구 사항이 생길 때마다 if
문을 넣으면 코드가 너무 지저분해진다.
void main() {
House house = houseService.getRandomHouse();
if (house.getOwner() != null && house.getOwner().getName() != null) {
System.out.println("onwer: " + house.getOwner().getName());
}
if (house.getAddress() != null) {
System.out.println("address: " + house.getAddress());
}
}
if
문으로 해결을 하면 당장 해결을 할 수는 있지만 요구사항이 추가될 때마다 코드가 지저분해져서 읽기가 싫어진다.하지만 Optional
을 쓰면 깔끔하게 코드를 작성할 수 있다.
void main() {
House house = houseService.getRandomHouse();
Optional.of(house)
.map(House::getOwner)
.map(Person::getName)
.ifPresent(name -> System.out.println("owner:" + name));
Optional.of(house)
.map(House::getAdress)
.ifPresent(address -> System.out.println("address:" + address));
}
여기서 집주인이 없으면 '없음'이라고 나오고, 주소도 없으면 '발급 되지 않음'이라고 나오게 해주세요라는 요구사항이 추가되면?
void main() {
House house = houseService.getRandomHouse();
String onwerName = Optional.of(house).map(House::getOnwer).map(Person::getName).orElse("없음");
String address = Optional.of(house).map(House::getAdress).orElse("발급 되지 않음");
System.out.println("onwer:" + onwerName);
System.out.println("address:" + address);
}
위와 같이 코드를 작성할 수 있다. 더 가독성이 좋아졌다.
Optional
객체를 생성하기 위해 사용해야 하는 메서드가 있다.
Optional.of
value 값이 null
인 경우 NPE
예외를 던진다. 반드시 값이 있어야 하는 경우 사용한다.
// 메서드 시그니처
public static <T> Optional<T> of(T value);
// 예제
Optional<String> opt = Optional.of("result");
Optional.ofNullable
value 값이 null
인 경우 비어있는 Optional
을 반환한다. 값이 null
일 수도 있는 경우 사용한다.
// 메서드 시그니처
public static <T> Optional<T> ofNullable(T value);
// 예제
Optional<String> opt = Optional.ofNullable(null);
Optional.empty
비어있는 옵셔널 객체를 생성한다. 조건에 따라 분기를 태워야하고 반환할 값이 없는 경우에도 사용한다.
// 메서드 시그니처
public static<T> Optional<T> empty();
// 예제
Optional<String> emptyOpt = Optional.empty();
옵서녈 객체를 생성한 뒤 사용 가능한 메서드이다. 이 메서드들은 다시 옵셔널을 반환하므로 메서드 체이닝을 통해 원하는 로직을 반복 삽입할 수 있다.
predicate 값이 참이면 해당 필터를 통과시키고 거짓이면 통과시키지 않는다.
// 메서드 시그니처
public Optional<T> filter(Predicate<? super T> predicate);
// 예제
Optional.of("True").filter((val) -> "True".eqauls(val)).orElse("NO DATA"); // "True"
Optional.of("False").filter((val) -> "True".eqauls(val)).orElse("NO DATA"); // "NO DATA"
mapper 함수를 통해 입력값을 다른 값으로 변환하는 메서드
// 메서드 시그니처
public<U> Optional<U> map(Function<? super T, ? extends U> mapper);
// 예제
Integer test = Optional.of("1").map(Integer::valueOf).orElseThrow(NoSuchElementException::new); // string to integer
mapper
함수를 통해 입력값을 다른 값으로 변환하는 메서드이다. map()
메서드와 다른 점은 메서드 시그니처 매개변수이다. map()
에서는 제네릭으로 U
를 정의했지만 flatMap()
에서는 Optional(U)
를 정의했다.
즉, flatMap()
메서드가 반환해야 하는 값은 Optional
이라는 것이다.
// 메서드 시그니처
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper);
// 예제
String result = Optional.of("result")
.flatMap((val) -> Optional.of("good"))
.get();
System.out.println(result); // print 'good'
기본값을 제공할 수 있는 공급자 함수를 정의할 수 있다. .orElseGet()
과 유사하지만 다른 점은 중간에 체이닝을 통해 우선 순위를 결정할 수 있다. .or()
연산 중에 비어있는 옵셔널이 된다면 다음 .or()
메서드로 진행하게 된다.
// 메서드 시그니처
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier);
// 예제
String result = Optional.ofNullable("test")
.filter(value -> "filter".equals(value))
.or(Optional::empty)
.or(() -> Optional.of("second"))
.orElse("final");
System.out.println(result); // print 'second'
이 메서드는 기존 자바8에서 옵셔널 객체가 바로 스트림 객체로 전환되지 않아 불편했던 부분을 해소시켜준다.
// 메서드 시그니처
public Stream<T> stream();
// 예제
List<String> result = List.of(1, 2, 3, 4)
.stream()
.map(val -> val % 2 == 0 ? Optional.of(val) : Optional.empty())
.flatMap(Optional::stream)
.map(String::valueOf)
.collect(Collectors.toList());
System.out.println(result); // print '[2, 4]'
종단 처리는 옵셔널 객체의 체이닝을 끝낸다는 것이다.
최종적으로 연산을 끝낸 후 값이 비어있지 않다면 입력값으로 주어진다. 이 값을 가지고 원하는 작업을 수행하면 된다. 하지만 연산을 하다 비어있는 옵셔널 객체를 받게 되면 .ifPresent()
메서드의 내용을 수행하지 않는다.
// 메서드 시그니처
public void ifPresent(Consumer<? super T> consumer);
// 예제1
Optional.of("test").ifPresent((value) -> {
// something to do
});
// 예제2 (ifPresent 미수행)
Optional.ofNullable(null).ifPresent((value) -> {
// nothing to do
});
최종적으로 연산을 끝낸 후 객체가 존재하는지 여부를 판별한다.
// 메서드 시그니처
public boolean isPresent();
// 예제
Optional.ofNullable("test").isPresent(); // true
Optional.ofNullable("test").filter((val) -> "result".eqauls(val)).isPresent(); // false
최종적으로 연산을 끝낸 후 객체를 꺼낸다. 이 때, 비어있는 옵셔널 객체였다면 예외가 발생한다.
// 메서드 시그니처
public T get();
// 예제
Optional.of("test").get(); // 'test'
Optional.ofNullable(null).get(); // NoSuchElementException!
최종적으로 연산을 끝낸 후에도 옵셔널 객체가 비어있다면 기본값으로 제공할 객체를 지정한다.
// 메서드 시그니처
public T orElse(T other);
// 예제
String result = Optional.ofNullable(null).orElse("default");
System.out.println(result); // print 'default'
최종적으로 연산을 끝낸 후에도 옵셔널 객체가 비어있다면 기본값으로 제공할 공급자 함수를 지정한다.
// 메서드 시그니처
public T orElseGet(Supplier<? extends T> other);
// 예제
String result = Optional.ofNullable("input").filter("test"::equals).orElseGet(() -> "default");
System.out.println(result); // print 'default'
최종적으로 연산을 끝낸 후에도 옵셔널 객체가 비어있다면 예외 공급자 함수를 통해 예외를 발생시킵니다.
// 메서드 시그니처
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X;
// 예제
Optional.ofNullable("input").filter("test"::equals).orElseThrow(NoSuchElementException::new);
ifPresent
와 유사하지만 한가지 매개변수를 더 받을 수 있다. 첫번째 매개변수인 action
은 유효한 객체를 받을 경우 실행하고, 두번째 매개변수인 emptyAction
은 유효한 객체를 받지 못한 경우에 실행한다.
// 메서드 시그니처
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction);
// 예제
Optional.ofNullable("test")
.ifPresentOrElse(value -> System.out.println(value), () -> System.out.println("null")); // print 'test'
Optional.ofNullable(null)
.ifPresentOrElse(value -> System.out.println(value), () -> System.out.println("null")); // print 'null'
매개변수가 필요없는 예외 메서드를 추가했다. 이 메서드를 사용하면 기존에는 아래처럼 호출해야 했던 코드를 간결하게 해준다.
// 메서드 시그니처
public T orElseThrow();
// 예제 (자바 8)
Optional.ofNullable(something).orElseThrow(NoSuchElementException::new);
// 예제 (자바 10)
Optional.ofNullable(something).orElseThrow();
메서드 | 설명 |
---|---|
of() | value 값이 null 인 경우 NPE 예외를 던진다. 반드시 값이 있어야 하는 경우 사용한다. |
ofNullable() | value 값이 null 인 경우 비어있는 Optional 을 반환한다. 값이 null 일 수도 있는 경우 사용한다. |
empty | 비어있는 옵셔널 객체를 생성한다. 조건에 따라 분기를 태워야하고 반환할 값이 없는 경우에도 사용한다. |
메서드 | 설명 |
---|---|
filter | predicate 값이 참이면 해당 필터를 통과시키고 거짓이면 통과시키지 않는다. |
map | mapper 함수를 통해 입력값을 다른 값으로 변환하는 메서드 |
flatMap | apper 함수를 통해 입력값을 다른 값으로 변환하는 메서드이다. |
or | 기본값을 제공할 수 있는 공급자 함수를 정의할 수 있다. .orElseGet() 과 유사하지만 다른 점은 중간에 체이닝을 통해 우선 순위를 결정할 수 있다. .or() 연산 중에 비어있는 옵셔널이 된다면 다음 .or() 메서드로 진행하게 된다. |
stream | 이 메서드는 기존 자바8에서 옵셔널 객체가 바로 스트림 객체로 전환되지 않아 불편했던 부분을 해소시켜준다. |
메서드 | 설명 |
---|---|
ifPresent | 최종적으로 연산을 끝낸 후 값이 비어있지 않다면 입력값으로 주어진다. 이 값을 가지고 원하는 작업을 수행하면 된다. 하지만 연산을 하다 비어있는 옵셔널 객체를 받게 되면 .ifPresent() 메서드의 내용을 수행하지 않는다. |
isPresent | 최종적으로 연산을 끝낸 후 객체가 존재하는지 여부를 판별한다. |
get | 최종적으로 연산을 끝낸 후 객체를 꺼낸다. 이 때, 비어있는 옵셔널 객체였다면 예외가 발생한다. |
orElse | 최종적으로 연산을 끝낸 후에도 옵셔널 객체가 비어있다면 기본값으로 제공할 객체를 지정한다. |
orElseGet | 최종적으로 연산을 끝낸 후에도 옵셔널 객체가 비어있다면 기본값으로 제공할 공급자 함수를 지정한다. |
orElseThrow | 최종적으로 연산을 끝낸 후에도 옵셔널 객체가 비어있다면 예외 공급자 함수를 통해 예외를 발생시킵니다. |
ifPresentOrElse | ifPresent 와 유사하지만 한가지 매개변수를 더 받을 수 있다. 첫번째 매개변수인 action 은 유효한 객체를 받을 경우 실행하고, 두번째 매개변수인 emptyAction 은 유효한 객체를 받지 못한 경우에 실행한다. |
orElseThrow | 매개변수가 필요없는 예외 메서드를 추가했다. 이 메서드를 사용하면 기존에는 아래처럼 호출해야 했던 코드를 간결하게 해준다. |