-
OptionalStaticPL/JAVA 2020. 6. 24. 16:39
1. Overview
Optional is a container object used to contain not-null objects. Optional object is used to represent null with absent value. This class has various utility methods to facilitate code to handle values as ‘available’ or ‘not available’ instead of checking null values. It is introduced in Java 8 and is similar to what Optional is in Guava.
API Note:
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.2. Description
2.1 Advantages
- Do not have to deal directly with nulls that can cause NPE.
- Don't have to manually do null checks.
- You can explicitly express the possibility that the variable may be null. (Thus, unnecessary defense logic can be reduced.)
2.2 Creating Optional Object
2.2.1 Use its empty() static method:
Obtain an empty Optional object that contains null. This empty object is an optional internally created singleton instance.
Optional<Member> maybeMember = Optional.empty();
2.2.2 Use its of() static method:
Create an Optional object containing a non-null object.
If null is passed, NPE is thrown, so use with caution.Optional<Member> maybeMember = Optional.of(aMember);
2.2.3 use the ofNullable() static method:
Creates an Optional object containing objects that are not sure whether it is null or not. You can think of it as a method that combines Optional.empty() and Optional.ofNullable(value). If null is passed, NPE is not thrown and an empty Optional object is obtained as in Optional.empty(). If you are not sure whether the object is null or not, you should use this method.
Optional<Member> maybeMember = Optional.ofNullable(aMember); Optional<Member> maybeNotMember = Optional.ofNullable(null);
2.3 Accessing objects contained in Optional
The Optional class provides various instance methods to retrieve the object it contains.
All of the methods below return the same value when an object containing Optional exists.
On the other hand, if the Optional is empty (i.e. contains null), it behaves differently.
Therefore, I will only explain the parts that work differently for the empty optional.2.3.1 get()
For empty Optional objects, throw NoSuchElementException.
2.3.2 orElse(T other)
For empty Optional objects, returns the argument passed.
2.3.3 orElseGet(Supplier<? extends T> other)
For empty Optional objects, it returns the object created by the passed-in functional argument.
You can think of it as a lazy version of orElse(T other).
You can expect a performance advantage over orElse(T other) since the function is only called when it is empty.2.3.4 orElseThrow(Supplier<? extends X> exceptionSupplier)
For empty Optional objects, it throws an exception created through the passed-in functional argument.
So far, we have quickly looked at the main methods provided by Optional.
Now let’s talk about how to use these methods.3. Example
3.1 Using orElse()/orElseGet()/orElseThrow() instread of isPresent()/get()
If you are a developer who can understand and use Optional correctly, you should be able to write the code of the first example in one line as follows.
In other words, the idea of treating null as a conditional statement must be completely changed to functional thinking.// Bad Optional<Member> member = ...; if (member.isPresent()) { return member.get(); } else { return null; } // Good Optional<Member> member = ...; return member.orElse(null); // Bad Optional<Member> member = ...; if (member.isPresent()) { return member.get(); } else { throw new NoSuchElementException(); } // Good Optional<Member> member = ...; return member.orElseThrow(() -> new NoSuchElementException());
// Bad public String getCityOfMemberFromOrder(Order order) { Optional<Order> maybeOrder = Optional.ofNullable(order); if (maybeOrder.isPresent()) { Optional<Member> maybeMember = Optional.ofNullable(maybeOrder.get()); if (maybeMember.isPresent()) { Optional<Address> maybeAddress = Optional.ofNullable(maybeMember.get()); if (maybeAddress.isPresent()) { Address address = maybeAddress.get(); Optinal<String> maybeCity = Optional.ofNullable(address.getCity()); if (maybeCity.isPresent()) { return maybeCity.get(); } } } } return "Seoul"; } // Good public String getCityOfMemberFromOrder(Order order) { return Optional.ofNullable(order) .map(Order::getMember) .map(Member::getAddress) .map(Address::getCity) .orElse("Seoul"); }
// Bad public Member getMemberIfOrderWithin(Order order, int min) { if (order != null && order.getDate().getTime() > System.currentTimeMillis() - min * 1000) { return order.getMember(); } } // Good public Optional<Member> getMemberIfOrderWithin(Order order, int min) { return Optional.ofNullable(order) .filter(o -> o.getDate().getTime() > System.currentTimeMillis() - min * 1000) .map(Order::getMember); }
3.2 Using orElseGet(() -> new ...) instead of orElse(new ...)
When method1(method2()) is executed, method2() is executed before method1() and always. Therefore, it is natural that new ... is always executed in orElse (new ...).
However, it may be due to the name, but strangely, if you use orElse(new ...) without thinking intentionally, you may feel that new ... will be executed only when there is no value in Optional.
Anyway, if there is no value in Optional, the value executed as the argument of orElse() is returned, so it has a meaning. However, if there is a value in Optional, the value executed as the argument of orElse() is ignored and discarded. Therefore, orElse(...) should only be used when ... is an already created or already calculated value without triggering a new object creation or new operation.
In orElseGet(Supplier), Supplier is executed only when there is no value in Optional. Therefore, there is no unnecessary overhead because a new object is created or a new operation is performed only when the optional has no value. Of course, there will be overhead for lambda expressions or method references, but it will be minimal compared to performing unnecessary object creation or operations.// Bad Optional<Member> member = ...; // new member() is executed unconditionally, whether member has value or not return member.orElse(new Member()); // Good Optional<Member> member = ...; // new Member() is executed only when member has no value return member.orElseGet(Member::new); // Good Member EMPTY_MEMBER = new Member(); ... Optional<Member> member = ...; // You can use orElse() for already created or calculated values return member.orElse(EMPTY_MEMBER);
3.3 Compare null instead of optional if you just want to get the value
// Bad return Optional.ofNullable(status).orElse(READY); // Good return status != null ? status : READY;
3.4 Return empty collection instead of optional
// Bad List<Member> members = team.getMembers(); return Optional.ofNullable(members); // Good List<Member> members = team.getMembers(); return members != null ? members : Collections.emptyList();
For the same reason, when declaring Spring Data JPA Repository method, it is not recommended to wrap the collection in an optional way as follows. The Spring Data JPA Repository method that returns a collection returns an empty collection instead of null, so there is no need to wrap it up as an Optional.
// Bad public interface MemberRepository<Member, Long> extends JpaRepository { Optional<List<Member>> findAllByNameContaining(String part); } // Good public interface MemberRepository<Member, Long> extends JpaRepository { List<Member> findAllByNameContaining(String part); // null이 반환되지 않으므로 Optional 불필요 }
3.5 Do not use Optional as a field
// Bad public class Member { private Long id; private String name; private Optional<String> email = Optional.empty(); } // Good public class Member { private Long id; private String name; private String email; }
3.6 Prohibit use of Optional as constructor or method argument
// Bad public class HRManager { public void increaseSalary(Optional<Member> member) { member.ifPresent(member -> member.increaseSalary(10)); } } hrManager.increaseSalary(Optional.ofNullable(member)); // Good public class HRManager { public void increaseSalary(Member member) { if (member != null) { member.increaseSalary(10); } } } hrManager.increaseSalary(member);
3.7 Prohibit use of Optional as an element of a collection
Collections can contain many elements. Therefore, it is better not to use the expensive Optional as an element, and to check the null when removing or using the element. In particular, Map provides methods including null checks such as getOrDefault(), putIfAbsent(), computeIfAbsent(), and computeIfPresent(), so it is better to use the method provided by Map instead of using Optional as an element of Map.
// Bad Map<String, Optional<String>> sports = new HashMap<>(); sports.put("100", Optional.of("BasketBall")); sports.put("101", Optional.ofNullable(someOtherSports)); String basketBall = sports.get("100").orElse("BasketBall"); String unknown = sports.get("101").orElse(""); // Good Map<String, String> sports = new HashMap<>(); sports.put("100", "BasketBall"); sports.put("101", null); String basketBall = sports.getOrDefault("100", "BasketBall"); String unknown = sports.computeIfAbsent("101", k -> "");
3.8 OptionalInt, OptionalLong, OptionalDouble instead of Optional<T>
If the value to be included in the optional is int, long, or double, do not use Optional<Integer>, Optional<Long>, Optional<Double>, which causes Boxing/Unboxing, and use OptionalInt, OptionalLong, OptionalDouble.
// Bad Optional<Integer> count = Optional.of(38); // boxing 발생 for (int i = 0 ; i < count.get() ; i++) { ... } // unboxing 발생 // Good OptionalInt count = OptionalInt.of(38); // boxing 발생 안 함 for (int i = 0 ; i < count.getAsInt() ; i++) { ... } // unboxing 발생 안 함
4. Reference
https://www.daleseo.com/java8-optional-effective/
https://www.geeksforgeeks.org/java-8-optional-class/
https://www.tutorialspoint.com/java8/java8_optional_class.htm
https://www.daleseo.com/java8-optional-before/
https://www.baeldung.com/java-optional
https://www.daleseo.com/java8-optional-after/
https://www.baeldung.com/java-optional-or-else-vs-or-else-get
'StaticPL > JAVA' 카테고리의 다른 글
Comparable, Comparator, and Collection Sort (0) 2020.03.15 Non-blocking, Lock-free operations (0) 2020.02.27 Semaphore (0) 2020.02.27 ReentrantReadWriteLock (0) 2020.02.27 ReentrantLock, lockInterruptibly, and tryLock (0) 2020.02.27