goodbye

Optional -1 본문

Java

Optional -1

goodbye 2022. 12. 26. 00:41

1. Optional 개요


Optional 은 결과가 없음을 나타나는 방법을 제공하는 라이브러리로 null 일수도 있는 객체를 감싸는 일종의 Wrapper 클래스를 의미합니다. Java8 에서 최초로 도입되었으며 null 때문에 발생하는 문제 대표적으로 NullPointerException 를 방지할 수 있도록 클래스를 통해 각종 메서드들을 제공해줍니다

💡
Optional is intended to provide a limited mechanism for library method return types where there needed to be a clear way to represent “no result," and using null for such was overwhelmingly likely to cause errors.

API Note: 공식 API 문서

Optional is primarily intended for use as a method return type where there is a clear need to represent “no result,” and where using null is likely to cause errors. A variable whose type is Optional should never itself be null; it should always point to an Optional instance.

메서드가 반환할 결과값이 ‘없음’을 명백하게 표현할 필요가 있고, null을 반환하면 에러를 유발할 가능성이 높은 상황에서 메서드의 반환 타입으로 Optional 을 사용하자는 것이 Optional 을 만든 주된 목적이다. Optional 타입의 변수의 값은 절대 null 이어서는 안 되며, 항상 Optional 인스턴스를 가리켜야 한다.

2. 생성 패턴 (static method)


/* 빈 Optional 객체 */
Optional.empty() 

/* 정적 팩토리 메서드로 Optional 객체 생성 */
Optional.of()

/* 객체가 null 이면 빈 Optional 객체 반환 */
Optional.ofNullable()

3. 메서드 종류


3. 1 Java 8


get() : Optional 의 값을 꺼낼 때 사용

public T get()
String someEmail = "some@naver.com";
Optional<String> maybeEmail = Optional.of(someEmail);
String email = maybeEmail.get(); 

Optional 객체의 값의 꺼내오려고 할 경우에 위와 같이 get() 메서드를 이용해서 간단하게 꺼낼수 있습니다.

다만 get() 메서드로 꺼내는것은 데이터의 NotNull 이 보장되는 경우만 사용하는것이 안전하며 null 일수도 있는 데이터를 다루는 경우에는 값이 없는 경우 default 값을 전달하도록 처리하거나 null 일때의 예외 처리를 하는 것이 바람직합니다

isPresent() : Optional 의 값이 있는지 확인 할때 사용

public boolean isPresent()
Optional<String> maybeEmail = Optional.empty();
if (maybeEmail.isPresent()) {
    return maybeEmail.get());
} else {
    return null;
}

기존에 null 체크를 Optional 객체의 메서드를 이용해서 간편하게 값 유무를 체크를 할 수 있습니다.

다만 null 체크 후에 값을 리턴하는 상황에서는 위와 같이 isPresent() 메서드를 이용하는것보다 아래에서 소개 할 orElse()orElseGet() 을 통해 값을 전달하는것이 좋습니다

orElse() : Optional 의 값이 없을때 대신 꺼낼값을 정의


public T orElse(T other)
Optional<String> maybeEmail2 = Optional.empty();
String defaultEmail = "default@naver.com";
String email = maybeEmail2orElse(defaultEmail);

orElse는 Optional 에 값이 없을때만 실행될것같지만 사실 Optional 에 값이 있거나 없거나 상관없이 항상 실행됩니다. 그리고 orElse 는 메서드를 인수로 받지 않고 값을 인수로 받아서 orElse() 메서드에 할당하기 때문에 Optional 에 값이 있다면 orElse() 의 인자로서 실행된 값이 무시되고 버려집니다. 따라서 orElse(…) 는 전달할 값이 새 객체 생성이나 새로운 연산을 유발하지 않고 이미 생성되었거나 이미 계산된 값일 때만 사용해야합니다

orElseGet() : Optional 안의 값이 null 이면 supplier로 공급되는값 리턴

public T orElseGet(Supplier<? extends T> supplier)
Optional<String> maybeEmail = Optional.empty();
String defaultEmail = "default@naver.com";
String email = maybeEmail.orElseGet(() -> defaultEmail);

orElseGet(Supplier) 에서 Supplier 는 Optional 에 값이 없을 때만 실행됩니다. 즉 Optional 에 값이 없을때만 새 겍체를 생성하거나 새 연산을 수행하기때문에 불필요한 오버해드가 없습니다. 물론 람다식이나 메서드 참조에 대한 오버헤드는 발생할 수 있으나 불필요한 객체를 생성하거나 연산을 수행하거는 케이스에 비하면 매우 낮은 비용에 해당 할 것입니다.

Optional<List<String>> strList = Optional.ofNullable(new ArrayList<String>());
List<String> list = strList.orElseGet(Collections::emptyList);

Collections.emptyList() 는 호출될때마다 비어있는 리스트를 반환하는 것이 아니라 이미 생성된 static qㅕㄴ수인 EMPTY_LIST를 반환하기때문에 orElse(Collections.emptyList()) 를 사용해도 무방합니다. 다만 이러한 안티 패턴보다는 orElseGet(Collections::emptyList() 를 사용하는것이 바람직합니다

orElseThrow() : Optionnal이 null이 아니라면 Optional 안의 값을, null이라면 exceptionSupplier 로 공급되는 exception을 던점

public <X extends Throwable> T orElseThrow(
    Supplier<? extends X> exceptionSupplier) throws X extends Throwable
Optional<String> maybeEmail = Optional.empty();
String email = maybeEmail.orElseThrow(() -> new RuntimeException("Email Not Present"));

ifPresent() : Optional 이 null 이 아니라면 action 실행

public void ifPresent(Consumer<? super T> action)
Optional<User> maybeUser = Optional.ofNullable(maybeGetUser(true));
        maybeUser.ifPresent(System.out::println);

public static User maybeGetUser(boolean returnUser) {
    if (returnUser) {
        return User.builder()
                .id(1)
                .name("Mary")
                .emailAddress("mary@gmail.com")
                .isVerified(false)
                .build();
    }
    return null;
}

null 이 아니기 때문에 User 객체의 내용이 출력된다

map() : Optional 의 null 이 mapper 를 적용

public <U> Optional<U> map(Function<? super T, ? extends U> mapper)
String userName = Optional.ofNullable(maybeGetUser(true))
        .map(User::getName)
        .map(name -> "The name is " + name)
        .orElse("Name is empty");

flatMap() : mapper 의 리턴값이 또 다른 Optional 이면 한단계의 Optional 되도록 처리

public <U> Optional<U> flatMap(Function<? super T,
	? extends Optional<? extends U>> mapper)
Optional<Integer> maybeId = Optional.ofNullable(maybeGetUser(true))
				.map(User::getId);
maybeId.ifPresent(System.out::println);

Optional 안의 Optional 을 출력하려고 하면 아래와 같이 출력됩니다

이것을 faltMap으로 바꾸어 리턴하면 다음과 같이 출력되는것을 확인 할 수 있습니다

Optional<String> maybeEmail = Optional.ofNullable(maybeGetUser(true))
        .flatMap(User::getEmailAddress);
System.out.println("maybeEmail = " + maybeEmail);

3. 2 Java 9


or() : 중간 처리 메서드로 기본값을 제공 할 수 있는 공급자 함수를 정의


public Optional<T> or(Supplier<T> supplier)
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier);

기존에 제공중인 .orElseGet() 과 유사하지만 다른 점은 중간에 체이닝을 통해 우선 순위를 결정 할 수 있습니다. 물론 .or() 연산 중에 비어있는 Optional 이 된다면 다음 .or() 메서드로 진행하게 됩니다.

String result = Optional.ofNullable("first")
        .filter(value -> "filter".equals(value))
        .or(Optional::empty)
        .or(() -> Optional.of("second"))
        .orElse("third");
System.out.println(result);
// print 'second'

ifPresentOrElse() : Optional 객체가 비어있을 경우 처리할 내용 정의


public void ifPresentOrElse(Consumer<? super T> action,
                            Runnable emptyAction)

Java9에서 추가된 ifPresendOrElse() 메서드는 Optional 객체가 값을 담고 있을때 처리 할 내용을 정의하는 ifPresent() 메서드의 기능을 제공하고, Optional 객체가 비어있을 경우 처리할 내용까지 정의 할 수 있습니다. 즉 ifPersent() 의 확장된 기능을 제공하는 메서드에 해당합니다.

ifPresentOrElse() 메서드는 두개의 인자를 받는데 첫번째 인자는 ifPresent() 메서드와 동일하며 두번째 인자는 Optional 객체가 비어있을 경우 수행될 람다 함수입니다.

@Builder
@ToString
public class User {
    private int id;
    private String name;
    private String emailAddress;
    private boolean isVerified;
    private List<Integer> friendUserIds;
    private LocalDateTime createAt;

    public static List<User> getUserList() {
        LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul"));
        User user1 = User.builder()
                .id(1)
                .name("Mary")
                .isVerified(true)
                .emailAddress("mary@naver.com")
                .createAt(now.minusDays(2))
                .friendUserIds(Arrays.asList(101, 102, 103, 104))
                .build();
		return Arrays.asList(user1);
}

ifPresent() 메서드를 이용하여 회원 정보중 이메일이 있는 경우에 이메일 정보를 출력하는 코드입니다.

Optional.ofNullable(user)
    .map(User::getEmailAddress)
    .ifPresent(email -> System.out.println("email: " + email));

인자값으로 Optional 객체가 담고 있는 값을 자기고 수행할 함수를 전달하기 때문에 user.getEmailAddress 가 null 을 리턴하거나 user 객체가 null 일 경우에는 Optional 객체가 empty 에 해당하여 아무것도 출력하지 않게 됩니다.

Optional.ofNullable(user)
    .map(User::getEmailAddress)
    .ifPresentOrElse(
            email -> System.out.println("send email:" + email),
            () -> {
                assert user != null;
                System.out.println("send email:" + user.getName());
            }
    );

ifPresentOrElse() 메서드를 사용하여 emailAddress 없을 경우에 유저의 이름을 출력 할수 있도록 코드를 변경하였습니다.

stream() : Optional 객체를 Stream 객체로 변환


Stream<Optional<T>> os = ..
     Stream<T> s = os.flatMap(Optional::stream)

stream() 메서드도 Java9 에서 추가되었으며 Optional 객체를 Stream 객체로 변환하기 위해서 사용됩니다.

Java8 에서 Optional 객체가 바로 스트림 객체로 전환되지 않아서 불편했던 부분을 해소시켜주기 위해 등장한 메서드로 실무에서 직접 사용해보면 Optional의 stream() 메서드는 단독으로 사용되기보다는 Stream flatMap 메서드와 함께 주로 사용되는것을 알 수 있습니다.

List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);
List<String> evenNumbers = numbers
        .stream()
        .map(number -> number % 2 == 0 ? Optional.of(number) : Optional.empty())
        .flatMap(Optional::stream)
        .map(String::valueOf)
        .toList();
System.out.println("evenNumbers = " + evenNumbers);

// evenNumbers = [2, 4, 6, 8]

위 코드는 숫자 배열을 stream 객체로 변환하여 짝수값만 가져오는 내용입니다.

3.3 JAVA 10


orElseThrow() : 매개변수가 필요없는 예외메서드


public T orElseThrow();

기존의 orElseThrow() 메서드는 exceptionSupplier 의 매를변수변닳 전달해야 했지만 Java10 에 추가된 orElseThrow() 메서드는 매개변수를 생략 할 수 있습니다

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

// Java 8
String email = maybeEmail.orElseThrow(() -> new RuntimeException("Email Not Present"));

// Java 10
String email = maybeEmail.orElseThrow();

Reference

💡

Uploaded by N2T

'Java' 카테고리의 다른 글

Optional -3  (0) 2023.01.20
Optional -2  (0) 2023.01.18
Java Stream & Function API  (0) 2022.12.21
Concurrency solution -1  (0) 2022.10.22
추상클래스와 인터페이스  (0) 2021.12.12
Comments