NullPointerException(NPE)
- Null 상태인 오브젝트를 레퍼런스할 때 발생
- Runtime error 이기 때문에 실행 전까지 발생 여부를 알기 쉽지 않음
Runtime 환경에서 에러가 발생할 경우, 심할 경우 서버 다운까지 가능하므로 발생시 리소스 비용이 만만치 않음
"Null pointer를 발명한 것은 나의 10억불짜리 실수였다" - Tony Hoare (출처 wikipedia)
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years
Optional ?
null 이거나, T타입 객체의 Wrapper class (Java 8 추가)
사용하는 이유
1. null을 직접 다루는 것은 위험, 예기치 못한 NPE 발생 -> Optional 통해서 NPE 처리, 하지만 사용할 경우 고려 필요 (참고)
2. null 체크 위한 if문 로직 필요 -> Optional 사용해서 코드 간소화
다만
- T타입 객체를 한번 더 감싸는 Wrapper Class가 생성되기 때문에 성능 면에서 저하
참고. Optional 학습 테스트 (Baeldung 예제)
https://github.com/ljw1126/java-ladder/blob/ljw1126/src/test/java/nextstep/study/OptionalTest.java
Optional<T> 클래스 메서드
Optional 생성
1. of(..)
- null이 아닌 Object를 이용해 Optional 생성
- private 생성자를 호출할 때 null 체크수행 (null 값 넣으면 NPE 발생)
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static <T> Optional<T> of(T value) {
return new Optional(value);
}
2. empty()
빈 Optional을 생성
public final class Optional<T> {
private static final Optional<?> EMPTY = new Optional();
private final T value;
private Optional() {
this.value = null;
}
public static <T> Optional<T> empty() {
Optional<T> t = EMPTY; // static 객체 할당, 값이 null
return t;
}
// ..
}
3. ofNullable(..)
- null인지 아닌지 알지 못하는 Object로 Optional 만들때 사용
- null 인 경우 EMPTY 객체 가져와 리턴
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
Optional 값 확인, 가져오기
1. isPresent()
Optional 안에 Object 가 null 인지 아닌지 판별 (true/false)
public boolean isPresent() {
return this.value != null;
}
2. get()
Optional 안에 value 값을 가져온다
public T get() {
if (this.value == null) {
throw new NoSuchElementException("No value present");
} else {
return this.value;
}
}
Object가 Null인 경우 NoSuchElementException 발생
@Test
void get() {
Optional<String> boxed = Optional.of("test");
Optional<String> empty = Optional.empty();
assertThat(boxed.get()).isEqualTo("test"); // Ok
assertThatThrownBy(empty::get).isInstanceOf(NoSuchElementException.class); // Ok
}
3. orElse(..)
- Optional 값이 null이 아니라면 값을 가져오고, null 이라면 other 인자로 전달받은 값(=객체) 리턴
- 삼항 연산자로 null 체크 하므로, 외부에서 if문을 사용할 필요 없어져 코드가 간결해짐
- 단, Wrapper Class를 사용하기 때문에 성능은 조금 떨어질 수 있음
public T orElse(T other) {
return this.value != null ? this.value : other;
}
4. orElseGet(..)
Optional 값이 null이 아니라면 값을 가져오고, null 인경우 suppiler 인자로 넘어온 값을 리턴
public T orElseGet(Supplier<? extends T> supplier) {
return this.value != null ? this.value : supplier.get();
}
5. orElseThrow(..)
Optional 값이 null이 아니면 값을 가져오고, null이라면 exceptionSuppiler 인자로 전달받은 예외를 발생 시킴
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (this.value != null) {
return this.value;
} else {
throw (Throwable)exceptionSupplier.get();
}
}
그외 Optional
1. ifPresent(..)
값이 존재하면 함수 실행하고, 없으면 아무 일 없음
public void ifPresent(Consumer<? super T> action) {
if (this.value != null) {
action.accept(this.value);
}
}
예시
@Test
void ifPresent() {
// given
Optional<String> given = Optional.ofNullable("홍길동");
Optional<String> empty = Optional.empty();
given.ifPresent(System.out::println); // 홍길동
empty.ifPresent(System.out::println);
}
2. map(..)
- Optional 값이 존재하면 mapper 함수를 실행한 결과 값을 리턴함
- Function 의 경우 T 타입의 인자를 받아 U 타입을 리턴하는 mapper 함수 뜻함
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
return !this.isPresent() ? empty() : ofNullable(mapper.apply(this.value));
}
예시
@Test
void map() {
// given
List<String> companyNames = Arrays.asList("oracle", "microsoft", "apple", "samsung");
Optional<List<String>> listOptional = Optional.of(companyNames);
String name = "baeldung";
Optional<String> nameOptional = Optional.of(name);
// when
int result1 = listOptional.map(List::size).orElse(0); // 4
int result2 = nameOptional.map(String::length).orElse(0); // 8
// then
assertThat(result1).isEqualTo(4);
assertThat(result2).isEqualTo(8);
}
3. flatMap(..)
- Optional 값이 있는 경우 mapper 함수 실행
- Function의 경우 T 타입 인자를 받아 Optional<? extends U> 반환하는 mapper 함수 뜻함
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
Objects.requireNonNull(mapper);
if (!this.isPresent()) {
return empty();
} else {
Optional<U> r = (Optional)mapper.apply(this.value);
return (Optional)Objects.requireNonNull(r);
}
}
예시
static class Person {
String name;
int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Optional<String> getName() {
return Optional.of(name);
}
public OptionalInt getAge() {
return OptionalInt.of(age);
}
}
@DisplayName("")
@Test
void flatMap() {
// given
Person person = new Person("고길동", 30);
Optional<Person> given = Optional.of(person);
// when
String name = given.flatMap(Person::getName).orElse("이름없음");
// then
assertThat(name).isEqualTo("고길동");
}
or, filter, stream 등은 직접 사용해 본 후 추후 기록
Primitive Type Optional 클래스
- Primitive Type을 한번 더 감싸는 Optional Wrapper Class
- 단순히 값을 다룬다면 Optional<T>보다 성능이 좀 더 나음
OptionalInt를 살펴보면 ..
- final 선언된 필드가 있고, 값이 있는 경우 isPresent = true, 없는 경우 false가 된다
- isPresent() 호출시 boolean isPresent 값에 따라 value의 존재 여부 판별
- equals() 의 경우 isPresent와 value을 비교해서 동일한지 판별
Java Optional – orElse() vs orElseGet()
// 임의 함수
public String getRandomName() {
LOG.info("getRandomName() method - start");
Random random = new Random();
int index = random.nextInt(5);
LOG.info("getRandomName() method - end");
return names.get(index);
}
orElse() 에 메서드 인자를 전달하는 경우
값이 존재하기 때문에 baeldung 만 가져올 것으로 생각되나, 인자로 전달한 메서드가 실행된 결과를 확인 가능하다
String name = Optional.of("baeldung").orElse(getRandomName()); // "baeldung"
[콘솔 출력]
getRandomName() method - start
getRandomName() method - end
추측으로 T 타입 인자에 대한 타입 추론으로 인해 메서드가 실행되는 것이 아닌가 싶다일반 메소드 호출이라서 즉시 실행되는 것으로 생각됨
orElseGet() 에 메서드 인자 전달하는 경우
- Optional 값이 있기 때문에 getRandomName() 메서드가 실행되지 않고 끝남
- 아래와 같이 값이 없는 경우에만 supplier.get()을 호출하여 실행하게 됨 (지연 실행)
public T orElseGet(Supplier<? extends T> supplier) {
return this.value != null ? this.value : supplier.get();
}
String name = Optional.of("baeldung").orElseGet(() -> getRandomName()); // "baeldung"
이와 같이 메서드 인자를 받았을 때 상대적으로 값이 있는데도 메서드가 실행되는 orElse() 성능이 뒤 떨어질 수 밖에 없다.
따라서 orElse() 사용할 때는 T 타입의 객체만 전달하여 사용하고, orElseGet()은 메서드를 전달하여 사용하는 것이 좋다.
참고
https://dzone.com/articles/using-optional-correctly-is-not-optional
https://www.baeldung.com/java-optional
https://www.baeldung.com/java-optional-or-else-vs-or-else-get
'공부 > Java' 카테고리의 다른 글
[Java] Stream API (0) | 2023.08.03 |
---|---|
[Java] 람다식을 더 짧게 - 메서드 참조 Method Reference (0) | 2023.08.02 |
[Java] Iterators 인터페이스 (자바의 정석) (0) | 2023.07.31 |
[Java] Arrays 클래스 메서드 (자바의 정석) (0) | 2023.07.31 |
[Java] String 클래스 메서드 (자바의 정석) (0) | 2023.07.31 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!