Optional
Optional
1
2
3
4
5
6
7
8
9
10
11
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을 잘못 사용하고 있었다는 느낌을 받았다.
기존 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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;
}
}
수정 코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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에서도 사용 가능하다.
1
2
3
4
5
6
7
8
9
10
11
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한 장점이 있는 대신 가독성이 떨어졌던 것 같았는데 정확히 기억이 나지 않아서 테스트 코드를 작성해봤다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
1
2
3
public T orElseGet(Supplier<? extends T> supplier) {
return value != null ? value : supplier.get();
}
파라미터로 Supplier
타입을 받게 되는데, Supplier
는 Lazy Evaluation 하기 때문에 사용할 때까지 실행하지 않는 것이다.
이 기사는 저작권자의
CC BY 4.0
라이센스를 따릅니다.