null 과 NPE(NullPointerException)
null은 참조형 타입의 기본 값이다. 어떠한 데이터 타입으로 구분되지 않는 모든 참조값에 할당될 수 있는 특수한 값이다.
null이 저장된 참조변수를 대상으로 메서드 호출 등의 코드를 수행할 경우 예외가 발생하는데 그 예외를 NPE(NullPointerException)이라고 한다. NPE가 문제가 되는 이유는 컴파일 시점에는 예외가 발생하지 않다가 런타임 때 발생하기 때문이다. 기존에 NPE를 막기위해서는 null 여부를 체크하는 코드를 추가했다.
String test = null;
int len = test.length(); // NPE 발생
if (String != null) { // null check
int len = test.length();
}
null check를 해야하는 인스턴스가 적다면 괜찮지만, 인스턴스가 많아지면 코드가 복잡해지고 가독성이 떨어지는 상황이 발생한다. 이러한 상황을 개선하기 위해 Java 8에서 Optional 클래스가 소개되었다.
Optional
public final class Optional<T> extends Object {
private final T value;
...
}
Optional은 위와 같이 정의되어 있다. null 이 될 수도 있는 인스턴스를 저장하는 일종의 래퍼(Wrapper) 클래스이다. Optional로 객체를 감싸서 사용하게 되면 null 체크를 직접 하지 않아도 되고, null을 직접 다루지 않아도 되므로 null을 안전하게 다룰 수 있다.
Optional 객체 선언
Optional<Member> member; // Member 타입의 Optional 변수
Optional<String> string; // String 타입의 Optional 변수
제네릭 타입을 제공하기 때문에, 선언과 동시에 타입 매개변수를 지정한다.
Optional 객체 생성
Optional 객체를 만들기 위한 정적 팩터리 메서드(static factory method)가 제공된다.
Optional.empty()
Optional<Member> member = Optional.empty();
비어있는 Optional 객체를 생성한다. 참조하더라도 NPE 같은 예외가 발생하지 않는다.
Opitonal.of(T value)
Optional<Member> member = Optional.of(aMember);
null이 아닌 객체를 담고 있는 Optional 객체를 생성한다. null이 넘어올 경우 NPE가 발생한다.
Optional.ofNullable(T value)
Optional<Member> member = Optional.ofNullable(aMember);
Optional<Member> member = Optional.ofNullable(null);
null일 수도 있는 객체를 담고있는 Optional 객체를 생성한다. null이 넘어올 경우 NPE가 발생하지 않고, 빈 Optional 객체를 생성한다.
Optional 중간 처리 메서드
Optional 객체를 여러 메서드를 사용하여 값을 필터링 하거나 다른 값으로 변환할 수 있다. 체이닝을 통해 여러 개의 중간 연산을 처리하고 최종적으로 종단 처리 메서드를 통해 결과를 얻을 수 있다.
filter
public Optional<T> filter(Predicate<? super T> predicate) { ... }
public Optinal<Person> filterName(Person person) {
return Optional.ofNullable(person)
.filter(p -> p.getName().equals("KimKim");
}
filter 메서드를 사용하면 원하는 값을 갖는 Optional 객체만 필터링 할 수 있다. 매개변수로 Predicate<T> 인터페이스를 받는다. 만일 일치하는 값이 없는 경우 빈 Optional 객체를 반환한다.
map
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) { ... }
public Optional<String> extractName(Person person) {
return Optional.ofNullable(person)
.map(p -> p.getName());
// 메서드 참조
// return Optional.ofNullable(person)
// .map(Person::getName);
}
map 메서드를 사용하면 원하는 형태로 Optional 객체를 변환할 수 있다. 매개변수로 Function<T, U> 인터페이스를 받는다. Optional 객체가 값을 가지고 있으면, map 메서드로 넘겨진 함수를 통해 값을 변경한다. Optional 객체가 비어있는 경우는 연산을 수행하지 않는다.
flatMap
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) { ... }
class Person {
private Optional<Phone> phone;
...
}
public Optional<Phone> testMap(Person person) {
return Optional.ofNullable(person)
.map(Person::getPhone) // Optional<Optional<Phone>> 타입
.get();
}
public Optional<Phone> testMap(Person person) {
return Optional.ofNullable(person)
.filtermap(Person::getPhone);
}
flatMap 메서드를 사용하면 중첩된 Optional 구조를 한 단계 없애고 단일 요소로 만든다. 매개변수로 Function<T, U> 인터페이스를 받는다. map은 람다식이 반환하는 내용물을 Optional 인스턴스로 감싸는 일을 알아서 해주지만, flatMap은 Optional 인스턴스로 감싸는 과정이 람다식에 포함되어야 하낟.
이러한 특성으로 flatMap은 클래스의 멤버로 Optional 인스턴스가 있는 경우에 유용하게 사용할 수 있다.
or
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) { ... }
Optional.ofNullable("Hi")
.filter(value -> value.length() > 3)
.or(() -> Optional.of("Hello"); // 빈 Optional 반환되면 Optional.of("Hello")로 대체
or 메서드는 값이 존재하는 경우 Optional 객체를 반환하고, 값이 없는 경우에는 대체할 수 있는 Optional 객체를 만들어 반환한다. 매개변수로 Supplier<T> 인터페이스를 받는다.
Optional 종단 처리 메서드
중간 연산을 통해 값을 필터링하거나 변환하는 체이닝 진행 후, 종단 연산 메서드를 통해 값을 꺼내거나 로직의 흐름을 바꾸는 등의 처리를 할 수 있다.
get
public T get() { ... }
String str1 = Optional.ofNullable("hi").get(); // str1 = "hi"
String str2 = Optional.ofNullable(null).get(); // NoSuchElementException 발생
get 메서드는 Optional 객체에서 값을 꺼낼 수 있는 메서드이다. 래핑된 값이 없는 경우에는 NoSuchElementException이 발생한다. 따라서 null이 아닌 객체를 담고 있을 때만 get 메서드를 사용해야 한다.
orElse
public T orElse(T other) { ... }
String str = Optional.ofNullable(null).orElse("hi"); // str = "hi"
orElse 메서드는 비어있는 Optional 객체에 기본값을 설정할 수 있다.
orElseGet
public T orElseGet(Supplier<? extends T> supplier) { ... }
String str = Optional.ofNullable(null).orElseGet(() -> "hi"); // str = "hi"
orElse 의 게으른 버전이다. orElseGet메서드는 매개변수로 Supplier<T> 인터페이스를 받는다. orElse는 항상 호출되는 반면, orElseGet은 비어있는 경우만 함수가 호출이 되기 때문에 성능상 이점을 기대할 수 있다.
orElseThrow
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X { ... }
String str = Optional.ofNullable(null)
.orElseThrow(() -> {
return new RuntimeException();
}); // 람다
String str = Optional.ofNullable(null)
.orElseThrow(RuntimeException::new); // 메서드 참조
메서드에 기본값을 설정하는 orElse와 orElseGet과는 달리, 비어있는 Optional 객체일 경우 예외를 발생시킨다.
isPresent
public boolean isPresent() { ... }
Optional<String> optStr = Optional.of(new String("hi"));
if(optStr.isPresent())
System.out.println(optStr.get()); // hi
isPresent 메서드는 Optional 객체에 값이 존재하면 true, 값이 없는 경우 false를 반환한다.
isEmpty
public boolean isEmpty() { ... }
Optional<String> optStr = Optional.ofNullable(null);
if(optStr.isEmpty())
System.out.println("hi"); // hi
isPresent 메서드의 반대 역할로, Optional 객체에 값이 없으면 true, 값이 존재하면 false를 반환한다.
ifPresent
public void ifPresent(Consumer<? super T> action) { ... }
Optional<String> optStr = Optional.of(new String("hi"));
optStr.ifPresent(s -> System.out.println(s)); // hi
ifPresent 메서드는 매개변수로 Consumer<T> 인터페이스를 받는다. Optional 객체에 값이 존재하는 경우 Consumer 를 실행하고, 값이 없는 경우는 아무 일도 일어나지 않는다. 위의 코드는 isPresent 메서드에서 사용한 예제를 ifPresent를 사용해 바꾼 예제 코드이다.
ifPresentOrElse
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) { ... }
Optional<String> optStr = Optional.ofNullable(null);
optStr.ifPresentOrElse(s -> System.out.println(s),
() -> System.out.println("empty")); // empty
ifPresentOrElse는 매개변수로 Consumer<T>와 Runnable을 받는다. Optional 객체에 값이 존재하는 경우 Consumer를 실행하고, 값이 없는 경우는 Runnbale을 실행한다.
OptionalXXX 클래스
Optional 클래스와 유사한 OptionalInt, OptionalLong, OptionalDouble 과 같은 클래스 들이 있다. 이들을 편의상 OptionalXXX 클래스라고 하자.
Optional<T>에서 T를 Integer, Long, Double로 구체화 한 것이 바로 OptionalInt, OptionalLong, OptionalDouble 클래스이다. 단, OptionalXXX 클래스에는 map과 flatMap 메서드가 정의되어 있지 않다.
참조
윤성우의 열혈 JAVA 프로그래밍
https://madplay.github.io/post/what-is-null-in-java
https://madplay.github.io/post/introduction-to-optional-in-java
https://madplay.github.io/post/how-to-handle-optional-in-java
https://madplay.github.io/post/how-to-return-value-from-optional-in-java
https://www.daleseo.com/java8-optional-after/
'JAVA > Java' 카테고리의 다른 글
[Java] 시각과 날짜의 처리 (1) | 2022.09.24 |
---|---|
[Java] Stream (1) | 2022.09.23 |
[Java] 메서드 참조(Method Reference) (0) | 2022.09.15 |
[Java] 기본 함수형 인터페이스(Functional Interface) (0) | 2022.09.13 |
[Java] 익명 클래스(Anonymous Class)와 람다식(Lambda Expression) (0) | 2022.09.13 |
댓글