Introduction of java8 optional

Keywords: Java Programming

background

NPE problems, 100% of Java programmers have encountered, and once was a pain in the heart.
In 1965, Tony Hoare introduced the Null reference in England, and subsequent design languages including Java kept this design.

An example

business model

Person has Car family and Car field,

Car, each car has Insurance, with Insurance field;

Insurance insurance. Each insurance has a name field;

Requirement: obtain the name of insurance purchased for a Person object;

Routine programming

    public String getCarInsuranceName(Person person) {
        return person.getCar().getInsurance().getName();
    }

Check programming

 public String getCarInsuranceName_check(Person person) {
        if (Objects.nonNull(person)) {
            final Car car = person.getCar();
            if (Objects.nonNull(car)) {
                final Insurance insurance = car.getInsurance();
                if (Objects.nonNull(insurance)) {
                    return insurance.getName();
                }
            }
        }
        return "unkown";
    }

Defensive programming

public String getCarInsuranceName_protect(Person person) {
        if (Objects.isNull(person)) {
            return "unkown";
        }
        final Car car = person.getCar();
        if (Objects.isNull(car)) {
            return "unkown";
        }
        final Insurance insurance = car.getInsurance();
        if (Objects.isNull(insurance)) {
            return "unkown";
        }
        return insurance.getName();
    }

Compare the following disadvantages:

Programming method shortcoming
Routine programming NPE problem
Check programming 1. Poor readability, multi-level if nesting; 2. Poor scalability, need to be familiar with the whole process, otherwise do not know which if to extend, it is easy to make mistakes;
Defensive programming 1. Difficult to maintain, 4 different exit points, easy to make mistakes, easy to miss inspection items

NPE's pain spot

  1. The most exceptions in java programs; none of them;
  2. It makes the amount of code bloated and disordered, and the empty judgment of objects is filled in the code, but it has no practical business significance;
  3. A back door of a type system does not belong to any type, or any type;
  4. It is meaningless in itself. It also destroys the idea of weakening pointer in java.

The modeling object of missing value in java8 is Optional, which can solve the pain point of NPE and design better API

Optional

Modeling evolution of domain model

  1. Person, which contains an Optional car field. A person may or may not have a car;
  2. Car, including an Optional insurance field. A car may or may not be insured;
  3. Insurance, an insurance company must have a name. It has a field name;

Construction method

Construction method Explain Remarks
Optional.empty() Must be an empty object Different from null, it is a singleton object
Optional.of(T t) It must be a non empty object If null value is given, NPE will be thrown immediately
Optioanl.ofNullable(T t) Allow empty objects to be placed inside Need to check before using values

map method - extract and transform values from objects

You can think of Optional as a single element stream or map, that is, you can convert the elements into other types or values after other operations according to certain rules. If there is no element, you can do nothing.

The following code is equivalent.

public class Test {
    public static final String UNKNOWN = "unknown";
    /**
     * traditional method
     * @param insurance
     * @return
     */
    public static String getInsuranceName(Insurance insurance){
        if (Objects.isNull(insurance)){
            return UNKNOWN;
        }
        return insurance.getName();
    }
    /**
     * map How to extract
     * @param insurance
     * @return
     */
    public static String getInsuranceNameOp(Insurance insurance){
       return Optional.ofNullable(insurance).map(Insurance::getName).orElse(UNKNOWN);
    }
}

flatMap method - convert to Optional object output;

The flatMap method, similar to Stream, cuts or combines elements into another Stream output.

    public static String getInsuranceName(Person person) {
        return Optional.ofNullable(person)
                .flatMap(Person::getCar)
                .flatMap(Car::getInsurance)
                .map(Insurance::getName).orElse(UNKNOWN);
    }

Default setting method (5 suitable for different scenarios)

Default method Explain scene
or(Supplier) If it is empty, an Optional object will be constructed later Some codes can be docked in a delayed way to generate default values
orElse(T t) If it is blank, the default value will be used Direct, simple
orElseGet(Supplier sp) If it is empty, it will be returned by function Delay return, you can dock some code logic
orElseThrow() If it is empty, it runs abnormally Default is NoSuchElementException
orElseThrow(Supplier sp) If it is empty, run out of custom exception Exception types can be customized

Usage value (get or consume)

There are mainly two scenarios: getting values directly and using get () method;

If there is value in it, consumption, ifPresent(Consumer c)

Methods of consumption or acquisition Explain scene
get() Get the value in Optional. If there is no value, an exception will be thrown Only after confirming that there is value in it can the guard be called
ifPresent(Consumer c) If there is a value, the user-defined code segment will be executed and the value will be consumed Stream programming, value continued processing logic
ifPresentOrElse(Consumer c , Runnable r) If there is value, then consumption, no value, other processing If there is value or no value, java9 can handle it

Multiple Optional operations

Through the use of flatMap, map can do this. What is executed in the method has done a good job in dealing with empty.

Examples are as follows:


    public static String getCheapestPrizeIsuranceNameOp(Person person, Car car) {
        return Optional.ofNullable(person)
                .flatMap(p -> Optional.ofNullable(car).map(c -> getCheapest(p, c)))
                .orElse(UNKNOWN);
    }

    public static String getCheapestPrizeIsuranceName(Person person, Car car) {
        if (Objects.nonNull(person) && Objects.nonNull(car)) {
            return getCheapest(person, car);
        }
        return UNKNOWN;
    }

    /**
     * Simulate to get the cheapest insurance
     *
     * @param person people
     * @param car    vehicle
     * @return Cheapest vehicle insurance name
     */
    private static String getCheapest(Person person, Car car) {
        return "pinan";
    }

filter method (filtering)

Because there is only one value in Optional, the filter here is actually to determine whether a single value is not.

Comparison code:

    public static Insurance getPinanInsurance(Person person){
        Optional<Insurance> insuranceOptional = Optional.ofNullable(person).map(Person::getCar).map(Car::getInsurance);
        if (insuranceOptional.isPresent() &&  Objects.equals("pinan", insuranceOptional.get().getName())){
            return insuranceOptional.get();
        }
        return null;
    }

    public static Insurance getPinanInsurance_filter(Person person){
        return Optional.ofNullable(person)
                .map(Person::getCar)
                .map(Car::getInsurance)
                .filter(item->Objects.equals(item.getName(),"pinan" ))
                .orElse(null);
    }

Empty method (construct an empty Optional object)

Optional modification history code

Encapsulate potentially null objects

    public Object getFromMap(String key){

        Map<String,Object> map = new HashMap<>(4);
        map.put("a", "aaa");
        map.put("b", "bbb");
        map.put("c", "ccc");

        Object value = map.get(key);
        if (Objects.isNull(value)){
            throw new NoSuchElementException("Non-existent key");
        }
        return value;

    }


    public Object getFromMapOp(String key){

        Map<String,Object> map = new HashMap<>(4);
        map.put("a", "aaa");
        map.put("b", "bbb");
        map.put("c", "ccc");

        Object value = map.get(key);

        return Optional.ofNullable(value).orElseThrow(()->new NoSuchElementException("Non-existent key"));
    }

The model with exception can be replaced with Optional object

This is the transformation of modeling ideas, not necessarily for everyone;

 /**
     * If the string is not a number, an exception is thrown
     * @param a Character string
     * @return number
     */
    public Integer string2Int(String a){
        return Integer.parseInt(a);
    }

    /**
     * Optional.empty Corresponding to the abnormal situation, it is easy to handle later;
     * @param a Character string
     * @return The integer that may fail to convert is delayed to the user.
     */
    public Optional<Integer> string2Int_op(String a){
        try{
            return Optional.of(Integer.parseInt(a));
        }catch (Exception ex){
            return Optional.empty();
        }
    }

Try not to use encapsulated Optional

Because there is only one element in Optional, there is no performance advantage in using encapsulation class, and important flatmap, map and filter methods are missing;

In general, the use of Optional simplifies the code and makes the code more readable and maintainable.

Here's an example:

    public Integer getFromProperties(Properties properties, String key) {
        String value = properties.getProperty(key);
        if (Objects.nonNull(value)) {
            try {
                Integer integer = Integer.parseInt(value);
                if (integer > 0) {
                    return integer;
                }
            } catch (Exception ex) {
                //No exception to handle
                return 0;
            }
        }
        return 0;
    }


    public Integer getFromProperties_op(Properties properties, String key) {
        return Optional.ofNullable(properties.getProperty(key))
                .map(item -> {
                    try {
                        return Integer.parseInt(item);
                    } catch (Exception ex) {
                        return 0;
                    }
                })
                .orElse(0);
    }

Optional source reading

A container object may or may not have a non null value. If the value exists, isPresent() returns true. If there is no value, the object is treated as null and isPresent() returns false;
More methods depend on whether the container contains a value, such as orelse (return a default value when there is no value)
ifPresent(Consumer c) is to execute an action when the value exists;

This is a value based class, which uses identity sensitive operations, including comparison reference = =, hashcode, and synchronization for an Optional object, which may have unexpected results, and should be avoided.

Note for API writing:
Optional was originally designed as the return value of a method when it was explicitly required to represent a situation where there was no value.
If NULL is returned, there may be an error; if Optional object is not a null object, it always points to an instance of Optional object.
/**
 * A container object which may or may not contain a non-{@code null} value.
 * If a value is present, {@code isPresent()} returns {@code true}. If no
 * value is present, the object is considered <i>empty</i> and
 * {@code isPresent()} returns {@code false}.
 *
 * <p>Additional methods that depend on the presence or absence of a contained
 * value are provided, such as {@link #orElse(Object) orElse()}
 * (returns a default value if no value is present) and
 * {@link #ifPresent(Consumer) ifPresent()} (performs an
 * action if a value is present).
 *
 * <p>This is a <a href="../lang/doc-files/ValueBased.html">value-based</a>
 * class; use of identity-sensitive operations (including reference equality
 * ({@code ==}), identity hash code, or synchronization) on instances of
 * {@code Optional} may have unpredictable results and should be avoided.
 *
 * @apiNote
 * {@code Optional} is primarily intended for use as a method return type where
 * there is a clear need to represent "no result," and where using {@code null}
 * is likely to cause errors. A variable whose type is {@code Optional} should
 * never itself be {@code null}; it should always point to an {@code Optional}
 * instance.
 *
 * @param <T> the type of value
 * @since 1.8
 */

Other codes are relatively simple. The model contains a value of type T. empty() is a special Optional object, and its value is null;

public final class Optional<T> {
    /**
     * Common instance for {@code empty()}.
     */
    private static final Optional<?> EMPTY = new Optional<>();

    /**
     * If non-null, the value; if null, indicates no value is present
     */
    private final T value;

    /**
     * Constructs an empty instance.
     *
     * @implNote Generally only one empty instance, {@link Optional#EMPTY},
     * should exist per VM.
     */
    private Optional() {
        this.value = null;
    }

Summary

  1. Optional represents an object that may be missing. The API can model based on this, but pay attention to serialization; avoid the problem of null pointer, and improve the readability and maintainability of the code.
  2. There are three construction methods of Optional, of, of nullable, empty;
  3. map, flatmap and filter can transform and filter values quickly;
  4. There are three methods to deal with missing values: orElse, orElseGet, orElseThrow;

Original is not easy, reprint please indicate the source.

Posted by nagasea on Sun, 17 Nov 2019 10:28:38 -0800