引言
对任何一位Java程序员来说,NullPointerException应该都遇到过。而且很多时候,线上出现问题,就是因为这个错,往往是由于我们判断遗漏。下面从一个简单的实例开始说起。
实例
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; } }
上述代码看起来相当正常,但是现实生活中很多人没有车。所以调用getCar方法的结果会怎样呢?
在实践中,一种比较常见的做法是返回一个null引用,表示该值的缺失,即用户没有车。
而接下来,对getInsurance的调用会返回null引用的insurance,这会导致运行时出现一个NullPointerException,终止程序的运行。
但这还不是全部。如果返回的person值为null会怎样?如果getInsurance的返回值也是null,结果又会怎样?
采用防御式检查减少NullPointerException
怎样做才能避免这种不期而至的NullPointerException呢?通常,我们会在需要的地方添加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检查,如果引用链上的任何一个遍历的解变量值为null,它就返回一个值为“Unknown”的字符串。
上述代码它不断重复着一种模式:每次不确定一个变量是否为null时,都需要添加一个进一步嵌套的if块,也增加了代码缩进的层数。很明显, 这种方式不具备扩展性,同时还牺牲了代码的可读性。
- 过多的退出语句
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();
}
上述代码避免深层递归的if语句块,采用了一种不同的策略:每次遇到null变量,都返回一个字符串常量“Unknown”。
这种方案远非理想,现在这个方法有了四个截然不同的退出点,使得代码的维护异常艰难。进一步而言,这种流程是极易出错的;如果你忘记检查了那个可能为null的属性会怎样?
null的种种问题
- NullPointerException是目前Java程序开发中典型的异常
- 它让代码充斥着深度嵌套的null检查,代码的可读性糟糕透顶。
- null自身没有任何的语义,尤其是,它代表的是在静态类型语言中以一种错误的方式对缺失变量值的建模。
- Java一直试图避免让程序员意识到指针的存在,唯一的例外是:null指针。
- null并不属于任何类型,这意味着它可以被赋值给任意引用类型的变量。这会导致问题, 原因是当这个变量被传递到系统中的另一个部分后,你将无法获知这个null变量初的赋值到底是什么类型。
Optional 类
Java 8中引入了一个新的类java.util.Optional。这是一个封装Optional值的类。变量存在时,Optional类只是对类简单封装。变量不存在时,缺失的值会被建模成一个“空” 的Optional对象,由方法Optional.empty()返回。
重新定义上述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; } }
代码中person引用的是Optional<Car>
, 而car引用的是Optional<Insurance>
,这种方式非常清晰地表达了一个person可能拥有也可能没有car的情形,同样,car可能进行了保险,也可能没有保险。
应用Optional
(1)创建Optional对象
- 声明一个空的Optional
Optional<Car> optCar = Optional.empty();
- 依据一个非空值创建Optional
Optional<Car> optCar = Optional.of(car);
如果car是一个null,这段代码会立即抛出一个NullPointerException,而不是等到试图访问car的属性值时才返回一个错误。
- 可接受null的Optional
Optional<Car> optCar = Optional.ofNullable(car);
如果car是null,那么得到的Optional对象就是个空对象。
(2)使用map 从 Optional 对象中提取和转换值
从对象中提取信息是一种比较常见的模式。比如,从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 对象
- 使用Optional获取car的保险公司名称
public String getCarInsuranceName(Optional<Person> person) {
return person.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}
由于包含嵌套的Optional,使用map无法通过编译,借助flatMap可以获取引用对象的值。
(4)使用filter剔除特定的值
在实际项目中,我们会经常遇到以下代码场景:
Insurance insurance = ...;
if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
System.out.println("ok");
}
使用Optional对象的filter方法,这段代码可以重构如下:
Optional<Insurance> optInsurance = ...;
optInsurance.filter(insurance ->"CambridgeInsurance".equals(insurance.getName())).ifPresent(x -> System.out.println("ok"));
Optional类方法
方法 | 描述 |
---|---|
empty | 返回一个空的 Optional 实例 |
filter | 如果值存在并且满足提供的谓词,就返回包含该值的 Optional 对象;否则返回一个空的 Optional 对象 |
flatMap | 如果值存在,就对该值执行提供的 mapping函数调用,返回一个 Optional 类型的值,否则就返 回一个空的 Optional 对象 |
get | 如果该值存在,将该值用 Optional 封装返回,否则抛出一个 NoSuchElementException 异常 |
ifPresent | 如果值存在,就执行使用该值的方法调用,否则什么也不做 |
isPresent | 如果值存在就返回 true,否则返回 false |
map | 如果值存在,就对该值执行提供的 mapping函数调用 |
of | 将指定值用 Optional 封装之后返回,如果该值为 null,则抛出一个 NullPointerException 异常 |
ofNullable | 将指定值用 Optional 封装之后返回,如果该值为 null,则返回一个空的 Optional 对象 |
orElse | 如果有值则将其返回,否则返回一个默认值 |
orElseGet | 如果有值则将其返回,否则返回一个由指定的 Supplier 接口生成的值 |
orElseThrow | 如果有值则将其返回,否则抛出一个由指定的 Supplier 接口生成的异常 |
总结
引入Optional 类的意图并非要消除每一个null引用。与此相反,它的目标是帮助你更好地设计出普适的API, 让程序员看到方法签名,就能了解它是否接受一个Optional的值。