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
- The most exceptions in java programs; none of them;
- 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;
- A back door of a type system does not belong to any type, or any type;
- 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
- Person, which contains an Optional car field. A person may or may not have a car;
- Car, including an Optional insurance field. A car may or may not be insured;
- 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
- 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.
- There are three construction methods of Optional, of, of nullable, empty;
- map, flatmap and filter can transform and filter values quickly;
- There are three methods to deal with missing values: orElse, orElseGet, orElseThrow;
Original is not easy, reprint please indicate the source.