《Java 8 in Action》Chapter 10:用Optional取代null
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]