1965 年, 英国一位名为 Tony Hoare 的计算机科学家在设计 ALGOL W 语言时提出了 null 引用的想法. ALGOL W 是第一批在堆上分配记录的类型语言之一. Hoare 选择 null 引用这种方式,"只是因为这种方法实现起来非常容易". 虽然他的设计初衷就是要 "通过编译器的自动检测机制, 确保所有使用引用的地方都是绝对安全的", 他还是决定为 null 引用开个绿灯, 因为他认为这是为 "不存在的值" 建模最容易的方式. 很多年后, 他开始为自己曾经做过这样的决定而后悔不已, 把它称为 "我价值百万的重大事物". 实际上, Hoare 的这段话低估了过去五十年来数百万程序员为修复空引用所耗费的代价. 近十年出现的大多数现代程序设计语言 1, 包括 Java, 都采用了同样的设计方式, 其原因是为了与更老的语言保持兼容, 或者就像 Hoare 曾经陈述的那样,"仅仅是因为这样实现起来更加容易".
1. 如何为确实的值建模
- 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();
- }
上面这段代码的问题就在于, 如果 person 没有车, 就会造成空指针异常.
1.1 采用防御式检查减少 NullPointerException
1.1.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";
- }
上述代码不具备扩展性, 同时还牺牲了代码的可读性.
1.1.2 过多的退出语句
- 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();
- }
这种模式中方法的退出点有四处, 使得代码的维护异常艰难.
1.2 null 带来的种种问题
它是错误之源. NullPointerException 是目前 Java 程序开发中最典型的异常. 它会使你的代码膨胀.
它让你的代码充斥着深度嵌套的 null 检查, 代码的可读性糟糕透顶.
它自身是毫无意义的. null 自身没有任何的语义, 尤其是是它代表的是在静态类型语言中以一种错误的方式对缺失变量值的建模.
它破坏了 Java 的哲学. Java 一直试图避免让程序员意识到指针的存在, 唯一的例外是: null 指针.
它在 Java 的类型系统上开了个口子. null 并不属于任何类型, 这意味着它可以被赋值给任意引用类型的变量. 这会导致问题, 原因是当这个变量被传递到系统中的另一个部分后, 你将无法获知这个 null 变量最初赋值到底是什么类型.
1.3 其他语言中 null 的替代品
Groovy 中的安全导航操作符
Haskell 中的 Maybe 类型
Scala 中的 Option[T]
2. Optional 类入门
变量存在时, Optional 类只是对类简单封装. 变量不存在时, 缺失的值会被建模成一个 "空" 的 Optional 对象, 由方法 Optional.empty() 返回. Optional.empty() 方法是一个静态工厂方法, 它返回 Optional 类的特定单一实例.
引入 Optional 类的意图并非要消除每一个 null 引用, 相反的是, 它的目标是帮助开发者更好地设计出普适的 API.
3. 应用 Optional 的几种模式
3.1 创建 Optional 对象
3.1.1 声明一个空的 Optional
正如前文已经提到, 你可以通过静态工厂方法 Optional.empty, 创建一个空的 Optional 对象:
Optional<Car> optCar = Optional.empty();
3.1.2 依据一个非空值创建 Optional
你还可以使用静态工厂方法 Optional.of, 依据一个非空值创建一个 Optional 对象:
Optional<Car> optCar = Optional.of(car);
如果 car 是一个 null, 这段代码会立即抛出一个 NullPointerException, 而不是等到你试图访问 car 的属性值时才返回一个错误.
3.2.3 可接受 null 的 Optional
最后, 使用静态工厂方法 Optional.ofNullable, 你可以创建一个允许 null 值的 Optional 对象:
Optional<Car> optCar = Optional.ofNullable(car);
如果 car 是 null, 那么得到的 Optional 对象就是个空对象.
3.2 使用 map 从 Optional 对象中提取和转换值
从对象中提取信息是一种比较常见的模式.
- 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.3 使用 flatMap 链接 Optional 对象
使用流时, flatMap 方法接受一个函数作为参数, 这个函数的返回值是另一个流. 这个方法会应用到流中的每一个元素, 最终形成一个新的流的流. 但是 flagMap 会用流的内容替换每个新生成的流. 换句话说, 由方法生成的各个流会被合并或者扁平化为一个单一的流.
- public String getCarInsuranceName(Optional<Person> person) { return person.flatMap(Person::getCar)
- .flatMap(Car::getInsurance)
- .map(Insurance::getName)
- .orElse("Unknown");
- }
3.4 默认行为及解引用 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>) 让你能在变量值存在时执行一个作为参数传入的方法, 否则就不进行任何操作.
3.5 两个 Optional 对象的组合
- 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();
- }
- }
3.6 使用 filter 剔除特定的值
filter 方法接受一个谓词作为参数. 如果 Optional 对象的值存在, 并且它符合谓词的条件, filter 方法就返回其值; 否则它就返回一个空的 Optional 对象.
- Insurance insurance = ...;
- if(insurance != null && "CambridgeInsurance".equals(insurance.getName())){
- System.out.println("ok");
- }
- Optional<Insurance> optInsurance = ...;
- optInsurance.filter(insurance ->
- "CambridgeInsurance".equals(insurance.getName()))
- .ifPresent(x -> System.out.println("ok"));
Optional 类中的方法进行了分类和概括:
4. 使用 Optional 的实战示例
4.1 用 Optional 封装可能为 null 的值
Optional<Object> value = Optional.ofNullable(map.get("key"));
每次你希望安全地对潜在为 null 的对象进行转换, 将其替换为 Optional 对象时, 都可以考虑使用这种方法.
4.2 异常与 Optional 的对比
- public static Optional<Integer> stringToInt(String s) {
- try {
- return Optional.of(Integer.parseInt(s));
- } catch (NumberFormatException e) {
- return Optional.empty();
- }
- }
我们的建议是, 你可以将多个类似的方法封装到一个工具类中, 让我们称之为 OptionalUtility. 通过这种方式, 你以后就能直接调用 OptionalUtility.stringToInt 方法, 将 String 转换为一个 Optional 对象, 而不再需要记得你在其中封装了笨拙的 try/catch 的逻辑了.
4.3 把所有内容结合起来
- 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;
- }
- // 优化版本
- public int readDuration(Properties props, String name) {
- return Optional.ofNullable(props.getProperty(name))
- .flatMap(OptionalUtility::stringToInt)
- .filter(i -> i> 0)
- .orElse(0);
- }
5. 小结
这一章中, 你学到了以下的内容.
null 引用在上被引入到程序设计语言中, 目的是为了表示变量值的.
Java 8 中引入了一个新的类 java.util.Optional, 对存在或缺失的变量值进行建模.
你可以使用静态工厂方法 Optional.empty,Optional.of 以及 Optional.ofNullable 创建 Optional 对象.
Optional 类支持多种方法, 比如 map,flatMap,filter, 它们在概念上与 Stream 类中对应的方法十分相似.
使用 Optional 会使你更积极地解引用 Optional 对象, 以应对变量值缺失的问题, 最终, 你能更有效地止代码中出现不而至的空指针异常.
使用 Optional 能帮助你设计更好的 API, 用户只需要阅读方法签名, 就能了解该方法是否接受一个 Optional 类型的值.
资源获取
公众号回复 : Java8 即可获取《Java 8 in Action》中英文版!
Tips
欢迎收藏和转发, 感谢你的支持!(๑•̀•́)و
来源: https://www.cnblogs.com/HelloDeveloper/p/11532028.html