Java 8 (9) Optional取代null

  NullPointerException,你们应该都见过。这是Tony Hoare在设计ALGOL W语言时提出的null引用的想法,他的设计初衷是想经过编译器的自动检测机制,确保全部使用引用的地方都是绝对安全的。不少年后,他对本身曾经作过的这个决定然后悔不已,把它称为“我价值百万的重大失误”。它带来的后果就是---咱们想判断一个对象中的某个字段进行检查,结果发现咱们查看的不是一个对象,而是一个空指针,他会当即抛出NullPointerException异常。java

 

看下面这个例子:安全

public class Person {
    private Car car;

    public Car getCar() {
        return car;
    }
}

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 p){
        return p.getCar().getInsurance().getName();
    }

这是一个获取保险公司名字的方法,可是在库里可能不少人没有车,因此会返回null引用。更没有车险,因此直接返回一个NullPointerException。工具

为了不这种状况,咱们通常会在须要的地方添加null的检查,而且添加的方式每每不一样。学习

 

避免NullPointerException第一次尝试:spa

    public String getCarInsuranceName(Person p){
        if(p != null){
            Car car = p.getCar();
            if(car != null){
                Insurance insurance = car.getInsurance();
                if(insurance != null){ return insurance.getName();
                }
            }
        }
        return "Unknown";
    }

 这个方法每次引用一个变量时,都会作一次null检查,若是任何一个返回值为null,则会返回Unknown。由于知道公司都必须有名字,因此最后一个保险公司的名字没有进行判断。这种方式不具有扩展性,同时还牺牲了代码的可读性。每一次都要嵌套一个if来进行检查。设计

 

避免NullPointerException第二次尝试:指针

    public String getCarInsuranceName(Person p) {
        if (p == null) return "Unknown";
        Car car = p.getCar();
        if (car == null) return "Unknown";
        Insurance insurance = car.getInsurance();
        if (insurance == null) return "Unknown"; return insurance.getName();
    }

第二种方式,避免了深层if语句块,采用了每次遇到null都直接返回Unknown字符串的方式。而后这个方案也并不是理想,如今这个方法有了四个大相径庭的退出点,使代码的维护更艰难。发生null时的默认值,在三个不一样的地方出现,不知道具体是哪一个返回null。code

 

Optional类orm

  Java 8中引入了一个新的类java.util.Optional<T>。这是一个封装Optional值的类。当变量存在时,Optional类知识对类简单封装,变量不存在时,缺失的值被建模成一个空的Optional对象,由方法Optional.empty()返回。该方法是一个静态工厂方法,返回Optional类的特定单一实例。

  null和Optional.empty()从语义上,能够当作是一回事。实际上它们之间的差异很是大:若是你尝试访问一个null,必定会触发null引用。而Optional.empty()能够在任何地方访问。

public class Person {
    private Optional<Car> car;

    public Optional<Car> getCar() {
        return car;
    }
}
public class Car {
    private Optional<Insurance> insurance;

    public Optional<Insurance> getInsurance() {
        return insurance;
    }
}

公司的名字咱们没有使用Optional<String> 而是保持了原类型String,那么它就必须设置一个值。

 

建立Optional对象

  1.声明一个空的Optional

Optional<Car> car = Optional.empty();

  2.依据一个非空值建立Optional

Car car = new Car();
Optional<Car> optCar = Optional.of(car);

  若是car是null,则直接会报错null引用,而不是等到你访问时。

  3.可接受null的Optional,这种方式与of不一样,编译器运行时不会报错。

Car car = null;
Optional<Car> optCar = Optional.ofNullable(car);

  

使用map从Optional对象中提取和转换值

  从对象中读取信息是一种比较常见的模式。好比,你能够从insurance公司对象中提取公司的名称。提取名称以前你须要检查insurance对象是否为null,如:

String name = null;
if(insurance != null){
    name = insurance.getName();    
}

为了支持这种模式,Optional提供了一个map方法。

Optional<Insurance> optionalInsurance = Optional.ofNullable(insurance);
Optional<String> name = optionalInsurance.map(Insurance::getName);

这里的map和流中的map相差无几。map操做会将提供的函数应用于流的每一个元素。你能够把Optional对象当作一种特殊的集合数据。如图:

这看齐来挺有用,可是如何应用起来,重构以前的代码呢?

p.getCar().getInsurance().getName();

 

使用flatMap连接Optional对象

  使用刚刚的学习的map,第一反应是重写以前的代码,好比这样:

Optional<Person> person = Optional.of(p);
        Optional<String> name = person
                .map(Person::getCar)
                .map(Car::getInsurance)
                .map(Insurance::getName);

可是这段代码没法经过编译,person是Optional<Person>类型的变量,调用map方法没有问题,可是getCar返回的是一个Optional<Car>类型的对象,这意味着map操做的结果的结果是一个Optional<Optinoal<Car>>类型的对象。 所以它调用getInsurance是非法的。

在流中使用flatMap能够扁平化合并流,在这里你想把两层的Optional合并为一个。

    public String getCarInsuranceName(Person p) {
        Optional<Person> person = Optional.of(p);
        return person
                .flatMap(Person::getCar)
                .flatMap(Car::getInsurance)
                .map(Insurance::getName)
                .orElse("Unknown");
    }

  经过代码的比较,处理潜在可能缺失的值时,使用Optional具备明显的优点。你能够很是容易实现指望的效果,不须要写那么多的条件分支,也不会增长代码的复杂性。

首先,Optional.of(p) 生成Optional<person>对象,而后调用person.flatMap(Person::GetCar)返回一个Optional<Car> 对象,Optional内的Person也被转换成了这种对象,结果就是两层的Optional对象,最终他们会被flatMap操做合并起来。若是合并时其中有一个为空,那么就构成一个空的Optional对象。若是给一个空的Optional对象调用flatMap返回的也是空的Optional对象。

而后,flatMap(Car::getInsurance) 会转换成Optional<Insurance> 合并。 第三步 这里调用的是map方法,由于返回类型是string 就不须要flatMap了。若是连上的任何一个结果为空就返回空,不然返回的值就是指望的值。 因此最后用了一个orElse的方法,当Optional为空的时候返回一个默认值。

 

获取Optional对象的值:

  1. get() 是这些方法中最简单但最不安全的方法。若是变量存在,直接返回封装的变量值。不然抛出一个NoSuchElementException异常。

  2. orElse(T other) 默认值,当值存在返回值,不然返回此默认值。

  3. orElseGet(Supplier<? extends T> other) 是orElse方法的延迟调用版,Supplier方法只有在Optional对象不含值时才执行调用。

  4. orElseThrow(Supplier<? extends X> exceptionSupplier )和get方法类似,遇到Optional对象为空时都抛出一个异常,使用orElseThrow能够自定义异常类型。

  5. ifPresent(Consumer<? super T>) 在变量值存在时执行,不然什么都不作。

  

判断Optional是否有值 isPresent()

假设有一个方法,接受两个参数 Person 和Car 来查询便宜的保险公司:

    public Insurance getInsurance(Person person ,Car car){
        //业务逻辑
        return new Insurance();
    }

这是之前的版本,使用咱们今天所学的知识 能够作一个安全版本,它接受两个Optional对象做为参数 返回值也是一个Optional<Insurance>方法:

public static Optional<Insurance> getInsuranceOpt(Optional<Person> person,Optional<Car> car){
        if(person.isPresent() && car.isPresent()){
            return Optional.of(getInsurance(person.get(),car.get()));
        }
        return Optional.empty();
    }

这看起来好了不少,更优雅的方式:

    public static Optional<Insurance> getInsuranceOpt1(Optional<Person> person, Optional<Car> car) {
        return person.flatMap(p -> car.map(c -> getInsurance(p, c)));
    }

若是p为空,不会执行返回空的Optional对象。若是car为空也不会执行 返回空Optional对象。 若是都有值那么调用这个方法。

 

filter剔除特定的值

  除了map和flatMap方法相似流中的操做,还有filter方法。使用filter能够快速判断Optional对象中是否包含指定的规则,如:

Insurance insurance = new Insurance();
if(insurance != null && insurance.getName().equals("abc")){
     System.out.println("is abc");
}

可使用filter改写为:

Optional<Insurance> insuranceOpt = Optional.of(insurance);
insuranceOpt.filter(c->c.getName().equals("abc")).ifPresent(x->System.out.println(x));

 

用Optional改善你的代码

  咱们虽然很难对老的Java API进行改动,可是能够再本身的代码中添加一些工具方法,来修复或者绕过这些问题,容纳给你的代码享有Optional带来的威力。

 

使用Optional封装可能为null的值

  现存的Java API几乎都是经过返回一个null的方式来表示须要值的缺失,或者因为某些缘由计算没法获得该值。好比,若是Map中不含指定的键对应的值,它的get就会返回一个null。咱们想在这种状况下返回Optional对象是很容易的。

Object value = new HashMap<String,Object>().get("key"); //null

有两种方式转换为Optional对象,第一种就是if else 方式,显然很笨重。第二种就是使用ofNullable方法。

Optional<Object> value = Optional.ofNullable(new HashMap<String,Object>().get("key"));

每次你但愿安全的对潜在为null的对象进行转换时,均可以先将其转换为Optional对象。

 

异常与Optional

  因为某种缘由,函数没法返回某个值,这时除了返回null,还会抛出一个异常。典型的例子是Integer.parseInt(String),将String转换为int。若是String没法解析为整型,就会抛出NumberFormatException异常。通常作这个操做,咱们会加入 try/catch来避免程序挂掉,而不是用if来判断。

  使用Optional对象对遭遇没法转换的String返回非法值进行建模,这时你指望parseInt的返回值是一个optional。虽然咱们没法改变之前的方法,但咱们能够建立一个工具方法:

    public static Optional<Integer> StringToInt(String s){
        try{
            return Optional.of(Integer.parseInt(s));
        }catch (Exception ex){
            return Optional.empty();
        }
    }

咱们能够创建一个OptionalUtils工具类,而后对全部的相似转换操做建立方法。而后在须要的地方 OptionalUtils.StringToInt(Stirng);

 

基础类型的Optional对象

  与Stream对象同样,Optional对象也提供了相似的基础类型:OptionalInt、OptionalDouble、OptionalLong。 可是这三个基础类型不支持map、flatMap、filter方法。

 

小结:

  1.null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。

  2.Java 8中加入了一个新的类 java.util.Optional<T> 对存在或缺失的变量进行建模。

  3.你可使用静态工厂方法Optional.empty、Optional.of、Optional.ofNullable建立Optional对象。

  4.Optional支持多种方法,好比map、flatMap、filter,他们在概念上与Stream相似。

  5.使用Optional会迫使你更积极的引用Optional对象,以及应对变量缺失的问题,最终你能更有效的防治代码中出现空指针异常。

  6.使用Optional能帮助你更好的设计API,用户只须要参阅签名酒知道该方法是否接受一个Optional。

相关文章
相关标签/搜索