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:

  1. OptionalAs a container to host objects, provide methods adapted to part of the functional interface, combined with part of the functional interface to provide method implementationNULLJudgment, filtering operations, safe values, mapping operations, and so on.
  2. OptionalThe 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.
  3. OptionalThis is a simplified tool that can solve node-null detection problems in multi-layer nested code (e.g. simplified arrow code).
  4. OptionalNot 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:

  1. OptionalJDK1.8 was introduced, the older JDK does not work, not all systems will be able to migrate smoothly to JDK1.8+.
  2. 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) :