The premise
Java.util.Optional is a class introduced in JDK8 that was ported from the well-known Java toolkit Guava. This article was written using JDK11. Optional is an object container that contains either NULL or non-null values. It is commonly used as a method return type that explicitly indicates that no result exists (which can also be Optional). This avoids possible exceptions (typically nullPointerExceptions) caused by NULL values. That is, a method whose return value type is Optional should avoid returning NULL and instead point to an Optional instance that contains the NULL object. Optional is an important milestone in the introduction of functional programming in Java by providing functional adaptation for NULL judgments, filtering operations, mapping operations, and more.
This article adds a new Asciidoc preview mode to get a feel for the official Spring documentation:
- Making Page: www.throwable.club/adoc/201908…
- Coding Page: throwable. Coding. Me/adoc / 201908…
Optional method source analysis and use scenarios
Optional’s source code is relatively simple, due to the fact that it is a simple object container. The following will analyze all its constructs, attributes, methods and corresponding usage scenarios based on the source code.
Optional properties and constructs
The Optional properties and constructs are as follows:
public final class Optional<T> {
// This is a generic Optional instance representing NULL values
private static finalOptional<? > EMPTY =new Optional<>();
// An object instance of a generic type
private final T value;
// instantiate Optional, note the private modifier, and set value to NULL
private Optional(a) {
this.value = null;
}
// Return directly to the inner EMPTY instance
public static<T> Optional<T> empty(a) {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
// Instantiate Optional with value, and throw NPE if value is NULL
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
// Instantiate Optional with value, throw NPE if value is NULL, in effect use Optional(T value)
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
// Return EMPTY if value is NULL, otherwise call Optional#of(value)
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
// Omit the rest of the code for now
}
Copy the code
If it is clear that an object instance is not NULL, use option #of(), for example:
Order o = selectByOrderId(orderId);
assert null! = o Optional op = Optional.of(o);Copy the code
If it is not clear whether an object instance is NULL, use option #ofNullable(), for example:
Optional op = Optional.ofNullable(selectByOrderId(orderId));
Copy the code
Specify that an Optional instance holding a NULL value can use option.empty ().
The get () method
// If value is null, NPE is thrown, otherwise value is returned
public T get(a) {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
Copy the code
The get() method is usually used when a value is not NULL. It does the existence of a prior value. Such as:
Order o = selectByOrderId(orderId);
assert null! = o Optional op = Optional.of(o); Order value = op.get();Copy the code
IsPresent () method
// Check whether value exists, true if not NULL, false if NULL
public boolean isPresent(a) {
returnvalue ! =null;
}
Copy the code
Here’s an example:
Order o = selectByOrderId(orderId);
boolean existed = Optional.ofNullable(o).isPresent();
Copy the code
IsEmpty () method
IsEmpty () is a method introduced in JDK11 that is the reverse judgment of isPresent() :
// If value exists, true is returned for NULL and false is returned for non-null
public boolean isEmpty(a) {
return value == null;
}
Copy the code
IfPresent () method
The ifPresent() method calls the Consumer#accept() method of the consumer functional interface if value is not NULL:
public void ifPresent(Consumer<? super T> action) {
if(value ! =null) { action.accept(value); }}Copy the code
Such as:
Optional.ofNullable(selectByOrderId(orderId)).ifPresent(o-> LOGGER.info("Order ID: {}",o.getOrderId());
Copy the code
IfPresentOrElse () method
IfPresentOrElse () is a new method in JDK9. It is an enhanced version of ifPresent(). If value is not NULL, the Consumer# Accept () method is invoked using value. If value is NULL then Runnable#run() is executed:
public void ifPresentOrElse(Consumer<? super T> action, Runnable emptyAction) {
if(value ! =null) {
action.accept(value);
} else{ emptyAction.run(); }}Copy the code
Such as:
String orderId = "xxxx";
Optional.ofNullable(selectByOrderId(orderId)).ifPresentOrElse(o-> LOGGER.info("Order ID: {}",o.getOrderId()), ()-> LOGGER.info("Order {} does not exist",o.getOrderId()));
Copy the code
The filter () method
public Optional<T> filter(Predicate<? super T> predicate) {
// Determine that predicate cannot be NULL
Objects.requireNonNull(predicate);
// If value is NULL, an empty instance is returned
if(! isPresent()) {return this;
} else {
// If the value is not NULL, the predicate returns itself. If the value is not NULL, the empty instance returns
return predicate.test(value) ? this: empty(); }}Copy the code
The function of this method is simple filtering function, container holding object value is not NULL will make a judgment, decide whether to return its own instance or empty(). Such as:
Optional.ofNullable(selectByOrderId(orderId)).filter(o -> o.getStatus() == 1).ifPresent(o-> LOGGER.info("Order {} is in status 1",o.getOrderId));
Copy the code
The map () method
Map () is a simple value mapping operation:
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
// Check that mapper cannot be NULL
Objects.requireNonNull(mapper);
// If value is NULL, empty() is returned.
if(! isPresent()) {return empty();
} else {
// Value is not NULL. Mapper converts the type to a nullable Optional instance
returnOptional.ofNullable(mapper.apply(value)); }}Copy the code
An example from an API comment:
List<URI> uris = ... ;// Find the path of unprocessed URIs in the URI listOptional<Path> p = uris.stream().filter(uri -> ! isProcessedYet(uri)).findFirst().map(Paths::get);Copy the code
FlatMap () method
The flatMap() method is also a mapping operation, but the value returned by the Optional type of the map is determined directly from the outside and does not need to be reencapsulated as an Optional instance by value:
public <U> Optional<U> flatMap(Function<? super T, ? extends Optional<? extends U>> mapper) {
// Mapper exists
Objects.requireNonNull(mapper);
// If value is NULL, empty() is returned.
if(! isPresent()) {return empty();
} else {
// value is not NULL, through mapper conversion, directly return mapper return value, make a NULL judgment
@SuppressWarnings("unchecked")
Optional<U> r = (Optional<U>) mapper.apply(value);
returnObjects.requireNonNull(r); }}Copy the code
Such as:
class OptionalOrderFactory{
static Optional<Order> create(String id){
/ / to omit...
}
}
String orderId = "xxx";
Optional<Order> op = Optional.of(orderId).flatMap(id -> OptionalOrderFactory.create(id));
Copy the code
Or () method
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier) {
// Supplier exists judgment
Objects.requireNonNull(supplier);
// if value is not NULL, return itself directly
if (isPresent()) {
return this;
} else {
// If value is NULL, the supplier returns the Optional instance provided by the supplier
@SuppressWarnings("unchecked")
Optional<T> r = (Optional<T>) supplier.get();
returnObjects.requireNonNull(r); }}Copy the code
Such as:
Order a = null;
Order b = select();
// Get the Optional package of the b order instance
Optional<Order> op = Optional.ofNullable(a).or(b);
Copy the code
The stream () method
// Nulls the value and converts it to Stream
public Stream<T> stream(a) {
if(! isPresent()) {return Stream.empty();
} else {
returnStream.of(value); }}Copy the code
OrElse () method
// If the value is not NULL, return value, otherwise return other
public T orElse(T other) {
returnvalue ! =null ? value : other;
}
Copy the code
OrElse () is a common way to provide a default backstop, such as:
String v1 = null;
String v2 = "default";
// get the default value for v2
String value = Optional.ofNullable(v1).orElse(v2);
Copy the code
OrElseGet () method
Supplier#get(); Supplier#get();
public T orElseGet(Supplier<? extends T> supplier) {
returnvalue ! =null ? value : supplier.get();
}
Copy the code
OrElseGet () is just an upgraded version of the orElse() method, for example:
String v1 = null;
Supplier<String> v2 = () -> "default";
// get the default value for v2
String value = Optional.ofNullable(v1).orElseGet(v2);
Copy the code
OrElseThrow () method
// If the value is NULL, NoSuchElementException is thrown, otherwise value is returned
public T orElseThrow(a) {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
// If the value is not NULL, return value, otherwise return the exception instance provided by Supplier#get()
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if(value ! =null) {
return value;
} else {
throwexceptionSupplier.get(); }}Copy the code
Such as:
Optional.ofNullable(orderInfoVo.getAmount()).orElseThrow(()-> new IllegalArgumentException(String.format("%s order amount cannot be NULL",orderInfoVo.getOrderId())));
Copy the code
Equals () and hashCode() methods
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if(! (objinstanceof Optional)) {
return false; } Optional<? > other = (Optional<? >) obj;return Objects.equals(value, other.value);
}
public int hashCode(a) {
return Objects.hashCode(value);
}
Copy the code
Both methods compare values, indicating that an Optional instance is the same KEY in a HashMap as long as the value is the same. Such as:
Map<Optional,Boolean> map = new HashMap<>();
Optional<String> op1 = Optional.of("throwable");
map.put(op1, true);
Optional<String> op2 = Optional.of("throwable");
map.put(op2, false);
/ / output is false
System.out.println(map.get(op1));
Copy the code
Optional field
Here are some common usage scenarios for Optional.
Empty judgment
NULL check is mainly used to set the properties of an object when it is not known whether the current object is NULL. When Optional is not used, the code looks like this:
if(null! = order){ order.setAmount(orderInfoVo.getAmount()); }Copy the code
The code for Optional is as follows:
Optional.ofNullable(order).ifPresent(o -> o.setAmount(orderInfoVo.getAmount()));
// If the empty object is OrderInfoVo as follows
Order o = select();
OrderInfoVo vo = ...
Optional.ofNullable(vo).ifPresent(v -> o.setAmount(v.getAmount()));
Copy the code
The advantage of using Optional to implement null judgments is that you can compress the code to a single line with only one property set, which makes the code relatively concise.
assertions
When maintaining older systems, in many cases external arguments do not have short judgments, so you need to write assertion code such as:
if (null == orderInfoVo.getAmount()){
throw new IllegalArgumentException(String.format("%s order amount cannot be NULL",orderInfoVo.getOrderId()));
}
if (StringUtils.isBlank(orderInfoVo.getAddress()){
throw new IllegalArgumentException(String.format("% S order address cannot be empty",orderInfoVo.getOrderId()));
}
Copy the code
The assertion code with Optional is as follows:
Optional.ofNullable(orderInfoVo.getAmount()).orElseThrow(()-> new IllegalArgumentException(String.format("%s order amount cannot be NULL",orderInfoVo.getOrderId())));
Optional.ofNullable(orderInfoVo.getAddress()).orElseThrow(()-> new IllegalArgumentException(String.format("% S order address cannot be empty",orderInfoVo.getOrderId())));
Copy the code
Comprehensive simulation case
Here is a simulation case, the simulation steps are as follows:
- Display the customer ID list to query the customer list.
- Query the order list based on the customer ID in the existing customer list.
- Convert to order DTO view list based on order list.
@Data
static class Customer {
private Long id;
}
@Data
static class Order {
private Long id;
private String orderId;
private Long customerId;
}
@Data
static class OrderDto {
private String orderId;
}
// simulate a customer query
private static List<Customer> selectCustomers(List<Long> ids) {
return null;
}
// simulate order query
private static List<Order> selectOrders(List<Long> customerIds) {
return null;
}
/ / the main method
public static void main(String[] args) throws Exception {
List<Long> ids = newArrayList<>(); List<OrderDto> view = Optional.ofNullable(selectCustomers(ids)) .filter(cs -> ! cs.isEmpty()) .map(cs -> selectOrders(cs.stream().map(Customer::getId).collect(Collectors.toList()))) .map(orders -> { List<OrderDto> dtoList =new ArrayList<>();
orders.forEach(o -> {
OrderDto dto = new OrderDto();
dto.setOrderId(o.getOrderId());
dtoList.add(dto);
});
return dtoList;
}).orElse(Collections.emptyList());
}
Copy the code
summary
Optional is essentially an object container with the following characteristics:
Optional
As a container to host objects, provide methods adapted to part of the functional interface, combined with part of the functional interface to provide method implementationNULL
Judgment, filtering operations, safe values, mapping operations, and so on.Optional
The common usage scenario is for wrapping the return value of a method, but also as a temporary variable to enjoy the convenience of a functional interface.Optional
This is a simplified tool that can solve node-null detection problems in multi-layer nested code (e.g. simplified arrow code).Optional
Not a silver bullet.
Here’s the arrowhead code, try using the usual method and Optional:
// Suppose VO has multiple levels, and each level does not know whether the parent node is NULL, as follows
// - OrderInfoVo
// - UserInfoVo
// - AddressInfoVo
// - address(属性)
// Suppose I want to assign a value to the address attribute, then arrow code will be generated.
// Conventional methods
String address = "xxx"; OrderInfoVo o = ... ;if(null! = o){ UserInfoVo uiv = o.getUserInfoVo();if (null! = uiv){ AddressInfoVo aiv = uiv.getAddressInfoVo();if (null! = aiv){ aiv.setAddress(address); }}}/ / use the Optional
String address = "xxx";
OrderInfoVo o = null;
Optional.ofNullable(o)
.map(OrderInfoVo::getUserInfoVo)
.map(UserInfoVo::getAddressInfoVo)
.ifPresent(a -> a.setAddress(address));
Copy the code
Using Optional to resolve arrowhead code, the mapping operation map() reduces the number of if and NULL decision branches and makes code simpler.
Some developers have proposed making the return type of the DAO method Optional. I am neutral on this issue for the following reasons:
Optional
JDK1.8 was introduced, the older JDK does not work, not all systems will be able to migrate smoothly to JDK1.8+.- Not everyone is crazy about functional programming, because it brings convenience and changes the logic of code reading (some might even argue that it makes code less readable).
The attachment
- Making Page: www.throwable.club/2019/08/07/…
- Coding Page: throwable. Coding. Me / 2019/08/07 /…
- Markdown or Asciidoc files: github.com/zjcscut/blo…
(C-2-D E-A-20190805 by Throwable)
Technical official account (Throwable Digest), push the author’s original technical articles from time to time (never plagiarize or reprint) :