Java8实战学习笔记(五)——高效 Java 8 编程(二)

一、用Optional取代null

常常遭遇到null引发的NullPointerException,怎么避免?

(一)、如何为缺失的值建模

eg:一个拥有汽车及汽车保险的客户。

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 person) {
    return person.getCar().getInsurance().getName();  
}
--> 调用getCar方法返回一个null引用,表示该值的缺失;对getInsurance的调用会返回null引用的insurance,这会导致运行时出现
一个NullPointerException,终止程序的运行。

1、采用防御式检查减少 NullPointerException

(1)、尝试一:在需要的地方添加非null判断进行操作
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";
}
  • 以上代码标记为“深层质疑”,原因是它不断重复着一种模式:每次不确定一个变量是否为null时,都需要添加一个进一步嵌套的if块,也增加了代码缩进的层数。
  • 这种方式不具备扩展性,同时还牺牲了代码的可读性。
(2)、尝试二:在需要的地方添加null判断进行操作
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();
}
  • 以上代码标记为“过多推出语句”

2、null带来的种种问题

 它是错误之源。NullPointerException是目前Java程序开发中最典型的异常。
 它会使你的代码膨胀。它让你的代码充斥着深度嵌套的null检查,代码的可读性糟糕透顶。
 它自身是毫无意义的。null自身没有任何的语义,尤其是,它代表的是在静态类型语言中以一种错误的方式对
缺失变量值的建模。
 它破坏了Java的哲学。Java一直试图避免让程序员意识到指针的存在,唯一的例外是: null指针。

 它在Java的类型系统上开了个口子。null并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量。这会导致问题,原因是当这个变量被传递到系统中的另一个部分后,你将无法获知这个null变量最初的赋值到底是什么类型。

3、其他语言中 null 的替代品

汲取Haskell和Scala的灵感, Java 8引入了一个名为java.util.Optional<T>的新的类。

(二)、Optional 类入门

java.util. Optional<T>是一个封装Optional值的类。
  • 变量不存在时,缺失的值会被建模成一个“空”的Optional对象,由方法Optional.empty()返回。 Optional.empty()方法是一个静态工厂方法,它返回Optional类的特定单一实例。
  • eg: 使用Optional重新定义Person/Car/Insurance的数据模型
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; }
}
public class Insurance {
    private String name;
    public String getName() { return name; }
}

(三)、应用 Optional 的几种模式

1、创建Optional对象

(1)、声明一个空的Optional
  • 通过静态工厂方法Optional.empty,创建一个空的Optional对象:
Optional<Car> optCar = Optional.empty();
(2)、依据一个非空值创建Optional
  • 使用静态工厂方法Optional.of,依据一个非空值创建一个Optional对象:
Optional<Car> optCar = Optional.of(car);
(3)、可接受null的Optional
  • 使用静态工厂方法Optional.ofNullable,可以创建一个允许null值的Optional对象:
Optional<Car> optCar = Optional.ofNullable(car);

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

  • 从对象中提取信息是一种比较常见的模式。
  • eg: 从insurance公司对象中提取公司的名称。提取名称之前,需要检查insurance对象是否为null。
String name = null;
if(insurance != null){
    name = insurance.getName();
}
--> 改进:使用Optional提供的map方法
Optional<Insurance> optInsurance = Optional.ofNullable(insurance);
Optional<String> name = optInsurance.map(Insurance::getName);

3、使用 flatMap 链接 Optional 对象


  • eg1:使用Optional获取car的保险公司名称
public String getCarInsuranceName(Optional<Person> person) {
    return person.flatMap(Person::getCar)
        .flatMap(Car::getInsurance)
        .map(Insurance::getName)
        .orElse("Unknown");
}
  • eg2: 使用Optional解引用串接的Person/Car/Insurance对象
从Person中解引用出Car,从Car中解引用出Insurance,从Insurance对象中解引用出包含insurance

公司名称的字符串。


4、默认行为及解引用 Optional 对象

Optional类提供了多种方法读取Optional实例中的变量值。

get():最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。所以,除非你非常确定Optional变量一定包含值,否则使用这个方法是个相当糟糕的主意。此外,这种方式即便相对于
嵌套式的
null检查,也并未体现出多大的改进。
orElse(T other):允许你在Optional对象不包含值时提供一个默认值。
orElseGet(Supplier<? extends T> other)orElse方法的延迟调用版,Supplier

方法只有在Optional对象不含值时才执行调用。

  • 若创建默认值是件耗时费力的工作,应该考虑采用这种方式(借此提升程序的性能);
  • 需要非常确定某个方法仅在应该考虑采用这种方式(借此提升程序的性能);
  • 若需要非常确定某个方法仅在应该考虑采用这种方式(借此提升程序的性能);
  • 若需要非常确定某个方法仅在Optional为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。
orElseThrow(Supplier<? extends X> exceptionSupplier) get 方法非常类似,它们遭遇 Optional 对象为空时都会抛出一个异常,但是使用 orElseThrow 你可以定制希望抛出的异常类型。
ifPresent(Consumer<? super T>):使 能在变量值存在时执行一个作为参数传入的方法,否则就不进行任何操作。

5、两个 Optional 对象的组合

eg: 接受一个Person和一个Car对象,并以此为条件对外部提供的服务进行查询,通过一些复杂的业务逻辑,试图找到满足该组合的最便宜的保险公司:

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();
    }
}
public Insurance findCheapestInsurance(Person person, Car car) {
    // 不同的保险公司提供的查询服务
    // 对比所有数据
    return cheapestCompany;
}

6、使用 filter 剔除特定的值

需要调用某个对象的方法,查看它的某些属性使用filter。

  • eg:需要检查保险公司的名称是否为“Cambridge-Insurance”。
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
    System.out.println("ok");
}
--> Optional改进:Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance -> "CambridgeInsurance".equals(insurance.getName()))
            .ifPresent(x -> System.out.println("ok"));
filter方法接受一个谓词作为参数。 如果Optional对象的值存在,并且它符合谓词的条件,filter方法就返回其值;否则它就返回一个空的Optional对象。
  • eg: Person/Car/Insurance 模型中, Person还提供了一个方法可以取得Person对象的年龄:
public String getCarInsuranceName(Optional<Person> person, int minAge) {
    return person.filter(p -> p.getAge() >= minAge)
                .flatMap(Person::getCar)
                .flatMap(Car::getInsurance)
                .map(Insurance::getName)
                .orElse("Unknown");
}

Optinal类的方法:


(四)、使用 Optional 的实战示例

1、用 Optional 封装可能为 null 的值

eg:假设一个Map<String, Object>方法,访问由key索引的值时,如果map中没有与key关联的值,该次调用就会返回一个null:

Object value = map.get("key");
--> Optional改进:
Optional<Object> value = Optional.ofNullable(map.get("key"));

2、异常与 Optional 的对比

由于某种原因,函数无法返回某个值,除了返回null, Java API比较常见的替代做法是抛出一个异常

典型例子:使用静态方法Integer.parseInt(String),将String转换为int。

public static Optional<Integer> stringToInt(String s) {
    try {
        return Optional.of(Integer.parseInt(s));
    } catch (NumberFormatException e) {
        return Optional.empty();
    }
}
  • 基础类型的Optional对象:OptionalInt、 OptionalLong以及OptionalDouble,不支持map、flatMap以及filter方法,避免使用。

3、把所有内容整合起来

创建一系列属性:

Properties props = new Properties();
props.setProperty("a", "5");
props.setProperty("b", "true");
props.setProperty("c", "-3");

从这些属性中读取一个值,使用方法的签名:

public int readDuration(Properties props, String name)
public int readDuration(Properties props, String name) {
    String value = props.getProperty(name);
    if (value != null) {
        try {
            int i = Integer.parseInt(value);
            if (i > 0) {
            return i;
            }
        } catch (NumberFormatException nfe) { }
    }
    return 0;
}
--> 使用Optional从属性中读取duration
public int readDuration(Properties props, String name) {
    return Optional.ofNullable(props.getProperty(name))
            .flatMap(OptionalUtility::stringToInt)
            .filter(i -> i > 0)
            .orElse(0);
}

(五)、小结

null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。
Java 8中引入了一个新的类java.util.Optional<T>,对存在或缺失的变量值进行建模。
可以使用静态工厂方法Optional.emptyOptional.of以及Optional.ofNullable创建Optional对象。
Optional类支持多种方法,比如mapflatMapfilter,它们在概念上与Stream类中对应的方法十分相似。
使用Optional会迫使你更积极地解引用Optional对象,以应对变量值缺失的问题,最终,你能更有效地防止代码中出现不期而至的空指针异常。
使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值。 

二、CompletableFuture:组合式异步编程






















二、用Optional取代null

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值