1965年,英国一位名为Tony Hoare的计算机科学家在设计ALGOL W语言时提出了null引用的想法。ALGOL W是第一批在堆上分配记录的类型语言之一。Hoare选择null引用这种方式,“只是因为这种方法实现起来非常容易”。虽然他的设计初衷就是要“通过编译器的自动检测机制,确保所有使用引用的地方都是绝对安全的”,他还是决定为null引用开个绿灯,因为他认为这是为“不存在的值”建模最容易的方式。很多年后,他开始为自己曾经做过这样的决定而后悔不迭,把它称为“我价值百万的重大失误”。我们已经看到它带来的后果——程序员对对象的字段进行检查,判断它的值是否为期望的格式,最终却发现我们查看的并不是一个对象,而是一个空指针,它会立即抛出一个让人厌烦的NullPointerException异常。
如何避免空指针异常
假设你需要处理下面这样的嵌套对象,这是一个拥有汽车及汽车保险的客户。
import java.util.*;public class Person {private Car car;public Car getCar() {return car;}}import java.util.*;public class Car {private Insurance insurance;public Insurance getInsurance() {return insurance;}}public class Insurance {private String name;public String getName() {return name;}}
那么,下面这段代码存在怎样的问题呢?
public String getCarInsuranceName(Person person){return person.getCar().getInsurance().getName();}
很显然,如果person引用的car的值是null,则会导致运行时出现一个NullPointerException。
采用防御式检查减少NullPointerException
//深层质疑public String getCarInsuranceName(Person person){if(person != null){Car car = person.getCar();if(car != null){Insurance insurance = car.getInsurance();if(insurance !=null){return insurance.getName();}}}return "Unknown";}//过多的退出语句public String getCarInsuranceName(Person person){if(person == null){return "Unknown";}Car car = person.getCar();if(car == null){return "Unknown";}Insurance insurance = car.getInsurance();if(insurance == null){return "Unknown";}return insurance.getName();}
Optional类入门
Java 8中引入了一个新的类java.util.Optional<T>。这是一个封装Optional值的类。

变量存在时,Optional类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空”的Optional对象,由方法Optional.empty()返回。Optional.empty()方法是一个静态工厂方法,它返回Optional类的特定单一实例。
//使用Optional重新定义Person/Car/Insurance的数据模型import java.util.*;public class Person {private Optional<Car> car;public Optional<Car> getCar() {return car;}}import java.util.*;public class Car {private Optional<Insurance> insurance;public Optional<Insurance> getInsurance() {return insurance;}}public class Insurance {private String name;public String getName() {return name;}}
应用Optional
声明一个空的Optional
Optional<Car> optCar = Optional.empty();
依据一个非空值创建Optional
Optional<Car> optCar = Optional.of(car);
可接受null的Optional
//如果car是null,那么得到的Optional对象就是个空对象。Optional<Car> optCar = Optional.ofNullable(car);
使用map从Optional对象中提取和转换值
String name = null;if(insurance != null){name = insurance.getName();}Optional<Insurance> optInsurance = Optional.ofNullable(insurance);Optionnal<String> name = optInsurance.map(Insurance::getName);ap(Insurance::getName);
使用flatMap链接Optional对象
public String getCarInsuranceName(Optional<Person> person){return person.flatMap(Person::getCar).flatMap(Car::getInsurance).map(Insurance::getName).orElse("Unknown");}


默认行为及解引用Optional对象
Optional类提供了多种方法读取Optional实例中的变量值。
get()是这些方法中最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。所以,除非你非常确定Optional变量一定包含值,否则使用这个方法是个相当糟糕的主意。此外,这种方式即便相对于嵌套式的null检查,也并未体现出多大的改进。
orElse(T other)是我们在代码清单10-5中使用的方法,正如之前提到的,它允许你在Optional对象不包含值时提供一个默认值。
orElseGet(Supplier<? extends T> other)是orElse方法的延迟调用版,Supplier方法只有在Optional对象不含值时才执行调用。如果创建默认值是件耗时费力的工作,你应该考虑采用这种方式(借此提升程序的性能),或者你需要非常确定某个方法仅在Optional为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。
orElseThrow(Supplier<? extends X> exceptionSupplier)和get方法非常类似,它们遭遇Optional对象为空时都会抛出一个异常,但是使用orElseThrow你可以定制希望抛出的异常类型。
ifPresent(Consumer<? super T>)让你能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。
两个Optional对象的组合
public Insurance findCheapestInsurance(Person person, Car car){////返回最便宜的保险公司return cheapestCompany;}public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person,Optional<Car> car){if(person.isPresent() && car.isPresent()){return Optional.of(findCheapestInsurance(person.get(),car.get());}else{return Optional.empty();}}//以不解包的方式组合两个Optional对象public Optional<Insurance> nullSafeFindCheapestInsurance(Optional<Person> person,Optional<Car> car){return person.flatMap(p-> car.map(c->findCheapestInsurance(p,c)));}
使用filter剔除特定的值
Insurance insurance = ...;if(insurance != null && "sss".equals(insurance.getName()){System.out.println("ok");}//Optional对象的filter方法重构Optional<Insurance> optInsurance= ...;optInsurance.filter(insurance -> "sss".equals(insurance.getName())).ifPresent(x-> System.out.println("ok"));






