一、用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对象
公司名称的字符串。
4、默认行为及解引用 Optional 对象
Optional类提供了多种方法读取Optional实例中的变量值。
get():最简单但又最不安全的方法。如果变量存在,它直接返回封装的变量值,否则就抛出一个NoSuchElementException异常。所以,除非你非常确定Optional变量一定包含值,否则使用这个方法是个相当糟糕的主意。此外,这种方式即便相对于
嵌套式的null检查,也并未体现出多大的改进。
orElse(T other):允许你在Optional对象不包含值时提供一个默认值。
orElseGet(Supplier<? extends T> other):是orElse方法的延迟调用版,Supplier
方法只有在Optional对象不含值时才执行调用。
- 若创建默认值是件耗时费力的工作,应该考虑采用这种方式(借此提升程序的性能);
- 若需要非常确定某个方法仅在应该考虑采用这种方式(借此提升程序的性能);
- 若需要非常确定某个方法仅在应该考虑采用这种方式(借此提升程序的性能);
- 若需要非常确定某个方法仅在Optional为空时才进行调用,也可以考虑该方式(这种情况有严格的限制条件)。
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.empty、 Optional.of以及Optional.ofNullable创建Optional对象。
Optional类支持多种方法,比如map、 flatMap、 filter,它们在概念上与Stream类中对应的方法十分相似。
使用Optional会迫使你更积极地解引用Optional对象,以应对变量值缺失的问题,最终,你能更有效地防止代码中出现不期而至的空指针异常。
使用Optional能帮助你设计更好的API,用户只需要阅读方法签名,就能了解该方法是否接受一个Optional类型的值。