NullPointerException,大家应该都见过。这是Tony Hoare在设计ALGOL W语言时提出的null引用的想法,他的设计初衷是想通过编译器的自动检测机制,确保所有使用引用的地方都是绝对安全的。很多年后,他对自己曾经做过的这个决定而后悔不已,把它称为“我价值百万的重大失误”。它带来的后果就是---我们想判断一个对象中的某个字段进行检查,结果发现我们查看的不是一个对象,而是一个空指针,他会立即抛出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;
}
}

下面这个方法有什么问题呢?

    public String getCarInsuranceName(Person p){
return p.getCar().getInsurance().getName();
}

这是一个获取保险公司名字的方法,但是在库里可能很多人没有车,所以会返回null引用。更没有车险,所以直接返回一个NullPointerException。

为了避免这种情况,我们一般会在需要的地方添加null的检查,并且添加的方式往往不同。

避免NullPointerException第一次尝试:

    public String getCarInsuranceName(Person p){
if(p != null){
Car car = p.getCar();
if(car != null){
Insurance insurance = car.getInsurance();
if(insurance != null){
return insurance.getName();
}
}
}
return "Unknown";
}

这个方法每次引用一个变量时,都会做一次null检查,如果任何一个返回值为null,则会返回Unknown。因为知道公司都必须有名字,所以最后一个保险公司的名字没有进行判断。这种方式不具备扩展性,同时还牺牲了代码的可读性。每一次都要嵌套一个if来进行检查。

避免NullPointerException第二次尝试:

    public String getCarInsuranceName(Person p) {
if (p == null) return "Unknown";
Car car = p.getCar();
if (car == null) return "Unknown";
Insurance insurance = car.getInsurance();
if (insurance == null) return "Unknown";
return insurance.getName();
}

第二种方式,避免了深层if语句块,采用了每次遇到null都直接返回Unknown字符串的方式。然后这个方案也并非理想,现在这个方法有了四个截然不同的退出点,使代码的维护更艰难。发生null时的默认值,在三个不同的地方出现,不知道具体是哪个返回null。

Optional类

  Java 8中引入了一个新的类java.util.Optional<T>。这是一个封装Optional值的类。当变量存在时,Optional类知识对类简单封装,变量不存在时,缺失的值被建模成一个空的Optional对象,由方法Optional.empty()返回。该方法是一个静态工厂方法,返回Optional类的特定单一实例。

  null和Optional.empty()从语义上,可以当做是一回事。实际上它们之间的差别非常大:如果你尝试访问一个null,一定会触发null引用。而Optional.empty()可以在任何地方访问。

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;
}
}

公司的名字我们没有使用Optional<String> 而是保持了原类型String,那么它就必须设置一个值。

创建Optional对象

  1.声明一个空的Optional

Optional<Car> car = Optional.empty();

  2.依据一个非空值创建Optional

Car car = new Car();
Optional<Car> optCar = Optional.of(car);

  如果car是null,则直接会报错null引用,而不是等到你访问时。

  3.可接受null的Optional,这种方式与of不同,编译器运行时不会报错。

Car car = null;
Optional<Car> optCar = Optional.ofNullable(car);

  

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

  从对象中读取信息是一种比较常见的模式。比如,你可以从insurance公司对象中提取公司的名称。提取名称之前你需要检查insurance对象是否为null,如:

String name = null;
if(insurance != null){
name = insurance.getName();
}

为了支持这种模式,Optional提供了一个map方法。

Optional<Insurance> optionalInsurance = Optional.ofNullable(insurance);
Optional<String> name = optionalInsurance.map(Insurance::getName);

这里的map和流中的map相差无几。map操作会将提供的函数应用于流的每个元素。你可以把Optional对象看成一种特殊的集合数据。如图:

这看齐来挺有用,但是如何应用起来,重构之前的代码呢?

p.getCar().getInsurance().getName();

使用flatMap链接Optional对象

  使用刚刚的学习的map,第一反应是重写之前的代码,比如这样:

Optional<Person> person = Optional.of(p);
Optional<String> name = person
.map(Person::getCar)
.map(Car::getInsurance)
.map(Insurance::getName);

但是这段代码无法通过编译,person是Optional<Person>类型的变量,调用map方法没有问题,但是getCar返回的是一个Optional<Car>类型的对象,这意味着map操作的结果的结果是一个Optional<Optinoal<Car>>类型的对象。 因此它调用getInsurance是非法的。

在流中使用flatMap可以扁平化合并流,在这里你想把两层的Optional合并为一个。

    public String getCarInsuranceName(Person p) {
Optional<Person> person = Optional.of(p);
return person
.flatMap(Person::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getName)
.orElse("Unknown");
}

  通过代码的比较,处理潜在可能缺失的值时,使用Optional具有明显的优势。你可以非常容易实现期望的效果,不需要写那么多的条件分支,也不会增加代码的复杂性。

首先,Optional.of(p) 生成Optional<person>对象,然后调用person.flatMap(Person::GetCar)返回一个Optional<Car> 对象,Optional内的Person也被转换成了这种对象,结果就是两层的Optional对象,最终他们会被flatMap操作合并起来。如果合并时其中有一个为空,那么就构成一个空的Optional对象。如果给一个空的Optional对象调用flatMap返回的也是空的Optional对象。

然后,flatMap(Car::getInsurance) 会转换成Optional<Insurance> 合并。 第三步 这里调用的是map方法,因为返回类型是string 就不需要flatMap了。如果连上的任何一个结果为空就返回空,否则返回的值就是期望的值。 所以最后用了一个orElse的方法,当Optional为空的时候返回一个默认值。

获取Optional对象的值:

  1. get() 是这些方法中最简单但最不安全的方法。如果变量存在,直接返回封装的变量值。否则抛出一个NoSuchElementException异常。

  2. orElse(T other) 默认值,当值存在返回值,否则返回此默认值。

  3. orElseGet(Supplier<? extends T> other) 是orElse方法的延迟调用版,Supplier方法只有在Optional对象不含值时才执行调用。

  4. orElseThrow(Supplier<? extends X> exceptionSupplier )和get方法相似,遇到Optional对象为空时都抛出一个异常,使用orElseThrow可以自定义异常类型。

  5. ifPresent(Consumer<? super T>) 在变量值存在时执行,否则什么都不做。

判断Optional是否有值 isPresent()

假设有一个方法,接受两个参数 Person 和Car 来查询便宜的保险公司:

    public Insurance getInsurance(Person person ,Car car){
//业务逻辑
return new Insurance();
}

这是以前的版本,使用我们今天所学的知识 可以做一个安全版本,它接受两个Optional对象作为参数 返回值也是一个Optional<Insurance>方法:

public static Optional<Insurance> getInsuranceOpt(Optional<Person> person,Optional<Car> car){
if(person.isPresent() && car.isPresent()){
return Optional.of(getInsurance(person.get(),car.get()));
}
return Optional.empty();
}

这看起来好了很多,更优雅的方式:

    public static Optional<Insurance> getInsuranceOpt1(Optional<Person> person, Optional<Car> car) {
return person.flatMap(p -> car.map(c -> getInsurance(p, c)));
}

如果p为空,不会执行返回空的Optional对象。如果car为空也不会执行 返回空Optional对象。 如果都有值那么调用这个方法。

filter剔除特定的值

  除了map和flatMap方法类似流中的操作,还有filter方法。使用filter可以快速判断Optional对象中是否包含指定的规则,如:

Insurance insurance = new Insurance();
if(insurance != null && insurance.getName().equals("abc")){
System.out.println("is abc");
}

可以使用filter改写为:

Optional<Insurance> insuranceOpt = Optional.of(insurance);
insuranceOpt.filter(c->c.getName().equals("abc")).ifPresent(x->System.out.println(x));

用Optional改善你的代码

  我们虽然很难对老的Java API进行改动,但是可以再自己的代码中添加一些工具方法,来修复或者绕过这些问题,容纳给你的代码享有Optional带来的威力。

使用Optional封装可能为null的值

  现存的Java API几乎都是通过返回一个null的方式来表示需要值的缺失,或者由于某些原因计算无法得到该值。比如,如果Map中不含指定的键对应的值,它的get就会返回一个null。我们想在这种情况下返回Optional对象是很容易的。

Object value = new HashMap<String,Object>().get("key"); //null

有两种方式转换为Optional对象,第一种就是if else 方式,显然很笨重。第二种就是使用ofNullable方法。

Optional<Object> value = Optional.ofNullable(new HashMap<String,Object>().get("key"));

每次你希望安全的对潜在为null的对象进行转换时,都可以先将其转换为Optional对象。

异常与Optional

  由于某种原因,函数无法返回某个值,这时除了返回null,还会抛出一个异常。典型的例子是Integer.parseInt(String),将String转换为int。如果String无法解析为整型,就会抛出NumberFormatException异常。一般做这个操作,我们会加入 try/catch来避免程序挂掉,而不是用if来判断。

  使用Optional对象对遭遇无法转换的String返回非法值进行建模,这时你期望parseInt的返回值是一个optional。虽然我们无法改变以前的方法,但我们可以创建一个工具方法:

    public static Optional<Integer> StringToInt(String s){
try{
return Optional.of(Integer.parseInt(s));
}catch (Exception ex){
return Optional.empty();
}
}

我们可以建立一个OptionalUtils工具类,然后对所有的类似转换操作创建方法。然后在需要的地方 OptionalUtils.StringToInt(Stirng);

基础类型的Optional对象

  与Stream对象一样,Optional对象也提供了类似的基础类型:OptionalInt、OptionalDouble、OptionalLong。 但是这三个基础类型不支持map、flatMap、filter方法。

小结:

  1.null引用在历史上被引入到程序设计语言中,目的是为了表示变量值的缺失。

  2.Java 8中加入了一个新的类 java.util.Optional<T> 对存在或缺失的变量进行建模。

  3.你可以使用静态工厂方法Optional.empty、Optional.of、Optional.ofNullable创建Optional对象。

  4.Optional支持多种方法,比如map、flatMap、filter,他们在概念上与Stream类似。

  5.使用Optional会迫使你更积极的引用Optional对象,以及应对变量缺失的问题,最终你能更有效的防治代码中出现空指针异常。

  6.使用Optional能帮助你更好的设计API,用户只需要参阅签名酒知道该方法是否接受一个Optional。

Java 8 (9) Optional取代null的更多相关文章

  1. 《Java 8 in Action》Chapter 10:用Optional取代null

    1965年,英国一位名为Tony Hoare的计算机科学家在设计ALGOL W语言时提出了null引用的想法.ALGOL W是第一批在堆上分配记录的类型语言之一.Hoare选择null引用这种方式,& ...

  2. 用optional取代null

    Java8引入了java.util.Optional<T>,它是一个封装的Optional值的类.变量存在时,Optional类只是对类简单封装.变量不存在时,缺失的值会被建模成一个空的O ...

  3. java8 用Optional取代null

    如何处理null 怎样做才能避免不期而至的NullPointerException呢?通常,可以在需要的地方添加null的检查(过于激进的防御式检查甚至会在不太需要的地方添加检测代码),并且添加的方式 ...

  4. map和flatmap的区别+理解、学习与使用 Java 中的 Optional

    转自:map和flatmap的区别 对于stream,   两者的输入都是stream的每一个元素,map的输出对应一个元素,必然是一个元素(null也是要返回),flatmap是0或者多个元素(为n ...

  5. 理解、学习与使用 Java 中的 Optional

    从 Java 8 引入的一个很有趣的特性是 Optional  类.Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) -- 每个 Java 程序员都 ...

  6. 理解、学习与使用 JAVA 中的 OPTIONAL<转>

    从 Java 8 引入的一个很有趣的特性是 Optional  类.Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都 ...

  7. JDK8新特性:使用Optional避免null导致的NullPointerException

    空指针异常是导致Java应用程序失败的最常见原因.以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写 ...

  8. 如何更好地使用Java 8的Optional

    Java 8中的Optional<T> 是一个可以包含或不可以包含非空值的容器对象,在 Stream API中很多地方也都使用到了Optional. java中非常讨厌的一点就是nullp ...

  9. Tomcat上java.lang.IllegalStateException: Optional int parameter 'id' is not present

    今日, 本人在tomcat+spring mvc平台的服务器上遇到java.lang.IllegalStateException: Optional int parameter 'id' is not ...

随机推荐

  1. UVA 437_The Tower of Babylon

    题意: 一堆石头,给定长宽高,每种石头均可以使用无数次,问这堆石头可以叠放的最高高度,要求下面的石头的长和宽分别严格大于上面石头的长和宽. 分析: 采用DAG最长路算法,由于长宽较大,不能直接用于表示 ...

  2. zoj2853 Evolution

    给定一个进化的矩阵图,问在m次之后最终的物种有多少个,实际上这和线性代数及其应用里的一个例题是一样的...总之就相当于煞笔的套个矩阵不断去乘m次,然后每次都会根据得到进化后各物种的个数,矩阵快速幂求一 ...

  3. POJ2586 Y2K Accounting Bug 解题报告

    Description Accounting for Computer Machinists (ACM) has sufferred from the Y2K bug and lost some vi ...

  4. Minimum Depth of Binary Tree(二叉树DFS)

    Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shor ...

  5. java web 验证码 第一次不正确的问题,解决方案

    首先是form表单 ,获取图片验证码 然后使用js 去服务器验证 问题: 第一次明明输入正确 ,确验证不了??那是因为你在form表单发起请求 和 ajax  发起的请求  地址 中 一个使用127. ...

  6. react 项目实战(二)创建 用户添加 页面 及 fetch请求 json-server db.json -w -p 8000

    1.安装 路由 npm install -S react-router@3.x 2.新增页面 我们现在的应用只有一个Hello React的页面,现在需要添加一个用于添加用户的页面. 首先在/src目 ...

  7. Objective-C之成魔之路【8-訪问成员变量和属性】

    郝萌主倾心贡献,尊重作者的劳动成果.请勿转载. 假设文章对您有所帮助,欢迎给作者捐赠,支持郝萌主,捐赠数额任意,重在心意^_^ 我要捐赠: 点击捐赠 Cocos2d-X源代码下载:点我传送 訪问成员变 ...

  8. UTF-8 的中文檔案名上傳問題

    在上传文件后,一般都会用 move_uploaded_file() 进行文件移动改名.但是 move_uploaded_file() 并不支持 UTF-8 编码,如果含有,那么函数就会执行失败,但是这 ...

  9. android学习笔记NO.5

    Intent 能够理解为信使 由Intent来协助完毕android各个组件之间的通讯. 感觉基础薄弱就又复习了一遍基础知识,整理了一遍! gen 保存自己主动生成的R资源目录 gen->com ...

  10. tflearn 保存模型重新训练

    from:https://stackoverflow.com/questions/41616292/how-to-load-and-retrain-tflean-model This is to cr ...