Bactoria 황준오

Optional

2019-08-08
bactoria

public static void main(String[] args) {
    Optional<String> strNotNull;
    strNotNull = "ee"; // compile error!
    strNotNull = Optional.ofNullable("ee"); // bad
    strNotNull = Optional.of("ee"); // best

    Optional<String> strNull;
    strNull = Optional.of(null); // bad
    strNull = Optional.ofNullable(null); // better
    strNull = Optional.empty(); // best
}

 

orNullable

Optional을 처음 접한건 JPA를 사용할 때였다. User에서 findById를 했을 때, 리턴값은 Optional<User> 였다.

만약 유저가 존재하지 않는다면 null 대신 Optional.empty()를 다뤄야 했기에,

if(user == null) 대신 if(user.isPresent()) 를 사용했다.

그런데 최근에 Optional을 잘못 사용하고 있었다는 느낌을 받았다.

 

기존 코드

public class UserService implements UserDetailsService {
    
    // ...
 
    private void validateDuplicationWithNotDeletedUsers(Optional<User> user) {
        if (user.isPresent()) {
            throw new UserAlreadyExistsException(user.get().getUserId());
        }
    }
    
    private boolean isSameProfile(User user, Attachment attachment) {
        if (user.getProfile() == null) {
            return false;
        }
        return user.getProfile().getAttachments().contains(attachment);
    }

    private boolean hasAttachment(UserModifyRequestDto requestDto) {
        return requestDto.getAttachment() != null && requestDto.getAttachment().getIdx() != null;
    }

    private boolean hasAttachment(UserSaveRequestDto requestDto) {
        return requestDto.getAttachment() != null && requestDto.getAttachment().getIdx() != null;
    }
}

 

수정 코드

public class UserService implements UserDetailsService {
    
    // ...

    private boolean isSameProfile(User user, Attachment attachment) {
        return Optional.ofNullable(user.getProfile())
                .map(profile -> profile.getAttachments().contains(attachment))
                .orElse(false);
    }

    private boolean hasAttachment(UserModifyRequestDto requestDto) {
        return Optional.ofNullable(requestDto.getAttachment())
                .map(Attachment::getIdx)
                .isPresent();
    }

    private boolean hasAttachment(UserSaveRequestDto requestDto) {
        return Optional.ofNullable(requestDto.getAttachment())
                .map(Attachment::getIdx)
                .isPresent();
    }
}

가독성이 좋아진 것 같다.

Optional이 제공하는 기능을 제대로 사용하지 않고 있었다.

 

(Collection)

collection에서도 사용 가능하다.

public class Application {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, null, 2, 3, 4, 5, null);

        Optional.ofNullable(numbers) // Optional<List<Integer>>
                .map(Collection::stream) // Optional<Stream<Integer>>
                .orElseGet(Stream::empty) // Stream<Integer> (1, null, 2, 3, 4, 5, null)
                .filter(Objects::nonNull) // Steram<Integer> (1, 2, 3, 4, 5)
                .forEach(System.out::print); // 12345
    }
}

   

orElse() 와 orElseGet()

이 둘중에 하나는 아규먼트가 lazy한 장점이 있는 대신 가독성이 떨어졌던 것 같았는데 정확히 기억이 나지 않아서 테스트 코드를 작성해봤다.

public class OptionalTest {
    @Test
    public void orElse_orElseGet_테스트() {
        Node node = new Node();
        Node emptyNode = null;

        Optional.ofNullable(emptyNode).orElse(new Node()); // Node 생성됨
        assertThat(Node.count).isEqualTo(1);

        Optional.ofNullable(emptyNode).orElseGet(() -> new Node()); // Node 생성됨
        assertThat(Node.count).isEqualTo(2);

        Optional.ofNullable(node).orElse(new Node()); // Node 생성됨
        assertThat(Node.count).isEqualTo(3);

        Optional.ofNullable(node).orElseGet(() -> new Node()); // Node 생성 안됨
        assertThat(Node.count).isEqualTo(3); // 4 아님!!
    }
}

class Node {
    static int count = -1;
    public Node() {
        count++;
    }
}

orElse는 값이 있든 없든 객체를 생성하지만, orElseGet은 값이 존재하지 않을 경우에만 실행시킨다.

 

Optional.java

    public T orElseGet(Supplier<? extends T> supplier) {
        return value != null ? value : supplier.get();
    }

파라미터로 Supplier 타입을 받게 되는데, SupplierLazy Evaluation 하기 때문에 사용할 때까지 실행하지 않는 것이다.


황준오 (Bactoria) 황준오 (Bactoria)

.

Comments

Content