[ Java ] Optional

5tr1ker·2023년 5월 10일
0

Java

목록 보기
2/6
post-thumbnail

NullPointerException

개발을 할 때 가장 많이 발생하는 예외중 하나가 NullPointerException 입니다. NPE 를 피하려면 null 여부를 확인해야 하는데, null 검사를 하는 코드가 많아지면 코드의 가독성을 떨어뜨릴 수 있습니다.

Optional 이란

Java8 부터 Optional 클래스가 NPE 을 방지할 수 있게 도와줍니다. Optional은 null이 올 수 있는 객체를 감싸는 클래스입니다. Optional 클래스는 내부에 value 값을 가지고 있어 값이 null 이더라도 NPE가 발생하지 않으며, 다양한 메서드를 제공해줍니다.

public final class Optional<T> {

private static final Optional<?> EMPTY = new Optional<>();
  // If non-null, the value; if null, indicates no value is present
  private final T value;
   
  ...
}

Optional 활용

Optional 생성

Optional.empty() : 값이 Null 인 Optional 객체

Optional은 Wrapper 클래스이기 때문에 값이 없을 수 있는데 Optional.empty() 를 사용해서 값이 Null 인 값을 생성할 수 있습니다.

Optional<String> optional = Optional.empty();

System.out.println(optional); // Optional.empty
System.out.println(optional.isPresent()); // false

Optional 클래스 내부에는 빈 값을 가지는 static 변수인 EMPTY 객체를 가지고 있습니다. 따라서 빈 객체를 여러개 생성해 주어야 하는 경우에는 EMPTY 객체를 공유하여 메모리를 절약합니다.

private static final Optional<?> EMPTY = new Optional<>();

Optional.of() : 값이 Null 이 아닌 Optional 객체

어떤 데이터가 Null이 아니라면 Optional.of() 로 객체를 생성할 수 있습니다. 만약 Null 데이터를 전달하면 NullPointerException 이 발생합니다.

Optional<String> optional = Optional.of("MyName");

Optional.ofNullable() : 값이 Null 일수도 있고 아닐 수도 있는 객체

만약 데이터가 Null이 올 수도 있고 아닐 수도 있는 경우에는 Optional.ofNullable() 로 생성할 수 있습니다. 이후에 orElse() 나 orElseGet() 메서드를 활용해서 값이 없는 경우에도 안전하게 가져올 수 있습니다.

Optional<String> optional = Optional.ofNullable(getName());

Optional 조회

Optional.get()

Optional 내부에 담긴 객체를 반환합니다. 만약 null인 객체라면 NoSuchElementException이 발생하기 때문에 isPresent() 로 확인 후에 가져옵니다.

Optional.orElseThrow()

Optional 내부에 담긴 객체가 Null이 아니라면 값을 반환하고, Null 이 라면 인자로 넘겨준 함수형 인터페이스에서 생성된 예외를 발생시킵니다.

Optional.orElse()

Optional 내부에 담긴 객체가 Null이 아니라면 객체를 반환하고, Null이라면 인자로 넘겨준 값을 반환합니다.

Optional.orElseGet()

Optional 내부에 담긴 객체가 Null이 아니라면 객체를 반환하고, Null이라면 인자로 넘겨준 함수형 인터페이스의 결과를 반환합니다.

orElse 와 orElseGet 차이

orElse 와 orElseGet은 Optional 값이 null인 경우에 실행되는 함수이지만 큰 차이점이 있습니다. 먼저 orElse는 파라미터로 값을 받으며 , orElseGet은 파라미터로 함수 인터페이스를 받습니다. 파라미터로 받는 값 뿐만 아니라 처리 순서에도 차이가 있습니다.

OrElse 는 .orElse(getUserEmail()); 다음과 같은 코드를 작성했다면 Null 여부 상관없이 getUserEmail() 함수를 실행한 결과값을 orElse에게 넘겨줍니다. 이후에 Null 이면 파라미터의 값을 넘겨주고 Null이 아니면 해당 값을 그대로 사용합니다.

반면에 orElseGet은 함수 인터페이스를 받기 때문에 null이 아니면 함수가 절대로 실행되지 않습니다.

orElse에 의한 발생가능한 장애 예시로 , 사용자가 없다면 사용자를 생성하는 .orElse(createUser()) 코드가 있다면 사용자가 존재해도 createUser() 메서드를 실행하기 때문에 Database 데이터 무결성 오류가 발생할 수 있습니다.

또한 문제가 없다 하더라도 orElse는 값을 생성하여 orElseGet 보다 비용이 크므로 사용을 피해야 합니다.

orElse 와 orElseGet 차이 정리

orElseorElseGet
파라미터로 값을 받는다.파라미터로 함수 인터페이스를 받는다.
값이 미리 존재하는 경우에 사용한다.값이 미리 존재하지 않는 경우에 사용한다.

Optional 주의 사항

Optional은 값을 Wrapping 하고 unWrapping 하며 null 인 경우에 대처하는 함수를 호출하는 등 오버헤더가 있으므로 잘못된 사용은 시스템 성능 저하를 일으킬 수 있습니다. 그렇기 때문에 Optional은 메서드의 반환 결과가 null이 될 수 있으며, Null 로 인해 오류가 발생할 수 있는 상황에서 반환값으로 사용되어야 합니다.

Optional 남용으로 발생할 수 있는 현상

NullPointerException이 아닌 NoSuchElementException 이 발생

Optional 내부에 값이 존재하는지 확인하지 않고 .get() 메서드를 통해 값을 가져온다면 NoSuchElementException 이 발생할 수 있습니다.

이전에 없었던 새로운 문제 발생

Optional은 직렬화( Serialize ) 를 지원하지 않아서 캐시나 메세지큐와 연동할 경우 문제가 발생할 수 있습니다.

물론 jackson 처럼 라이브러리에 Optional이 있을 경우 wrap , unwrap 하도록 지원해주지만 라이브러리의 스펙을 파악해야 한다는 문제점이 있습니다.

코드의 가독성을 떨어뜨림

Optional을 함수의 파라미터로 받는다면 파라미터 값 자체가 null인지 확인을 해야 하며, 파라미터의 제네릭 타입도 명시해야 하기 때문에 불필요한 코드가 늘어날 수 있습니다.

시간적 , 공간적 비용 ( 오버 헤드 ) 증가

Optional 는 값을 감싸는 컨테이너 클래스이기 때문에 추가적인 공간 비용이 필요하며, Optional 안에 있는 값을 얻기 위해서 Optional 객체에 접근해야 하므로 접근 비용도 증가합니다.

Optonal을 사용하면 단순 값을 사용하는 것 보다 메모리 사용량이 4배 증가합니다.

올바른 Optional 사용법

Optional 변수에 Null을 할당하지 마라

Optional 변수에 Null을 할당하면 해당 객체가 Null인지 추가로 확인을 해야 합니다. 따라서 값이 없는 경우에 Optional.empty() 를 사용합니다.

값이 없을 때는 Optional.orElse... 로 기본값을 사용하라

Optional의 장점중 하나는 함수형 인터페이스를 통해 가독성 좋고 유연한 코드를 작성하는 것입니다. 따라서 isPresent() 로 확인해서 get() 으로 가져오기 보다는 orElseGet 등을 활용합니다.

  • orElse() : 존재하는 값 변환
  • orElseGet() : 값이 준비되어 있지 않는 경우
  • orElseThrow() : 값이 없을 경우에 예외 발생

단순히 값을 얻기 위해 Optional을 사용하지 마라

단순히 값을 얻기 위해 Optional을 사용하면 오버 헤드가 증가합니다. 이러한 경우에 Optional을 사용하지 않고 직접 값을 다룹니다.

String name = ... ;
    
// WORST
return Optional.ofNullable(name).orElse("Default");

// BEST
return name == null ? "default" : name;

파라미터로 Optional을 넘기지 마라

Optional을 파라미터로 넘기면 객체가 null인지 검사를 해며, 이는 코드의 가독성을 떨어뜨릴 수 있습니다. Optional은 반환 타입으로 사용해야 하며 또한 Serializable 지원하지 않으므로 필드에 사용하지 않아야 합니다.

Collection은 빈 Collection을 활용하라

빈 Collection인 경우 Optional로 감쌀 필요가 없으며, 단순히 빈 Collection을 반환하는것이 가볍고 깔끔합니다.

반환 타입으로만 사용하라

Optional은 반환 타입으로써 오류가 발생할 수 있는 경우에 결과 없음을 보내주기 위해 만들어졌으며, Stream API와 결합하여 유연한 체이닝 API를 만들기 위해 탄생되었습니다.
만약 조회 결과로 Null을 반환하는것 보단 값의 유무를 반환하기 위해 설계되었기 때문에 목적에 맞게 사용되어야 합니다.

참고

참고 블로그 1 : https://mangkyu.tistory.com/70
참고 블로그 2 : https://mangkyu.tistory.com/203
참고 블로그 3 : https://dbbymoon.tistory.com/3

profile
https://github.com/5tr1ker

0개의 댓글