Java 8 (9) Optional取代null
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的更多相关文章
- 《Java 8 in Action》Chapter 10:用Optional取代null
1965年,英国一位名为Tony Hoare的计算机科学家在设计ALGOL W语言时提出了null引用的想法.ALGOL W是第一批在堆上分配记录的类型语言之一.Hoare选择null引用这种方式,& ...
- 用optional取代null
Java8引入了java.util.Optional<T>,它是一个封装的Optional值的类.变量存在时,Optional类只是对类简单封装.变量不存在时,缺失的值会被建模成一个空的O ...
- java8 用Optional取代null
如何处理null 怎样做才能避免不期而至的NullPointerException呢?通常,可以在需要的地方添加null的检查(过于激进的防御式检查甚至会在不太需要的地方添加检测代码),并且添加的方式 ...
- map和flatmap的区别+理解、学习与使用 Java 中的 Optional
转自:map和flatmap的区别 对于stream, 两者的输入都是stream的每一个元素,map的输出对应一个元素,必然是一个元素(null也是要返回),flatmap是0或者多个元素(为n ...
- 理解、学习与使用 Java 中的 Optional
从 Java 8 引入的一个很有趣的特性是 Optional 类.Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) -- 每个 Java 程序员都 ...
- 理解、学习与使用 JAVA 中的 OPTIONAL<转>
从 Java 8 引入的一个很有趣的特性是 Optional 类.Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException) —— 每个 Java 程序员都 ...
- JDK8新特性:使用Optional避免null导致的NullPointerException
空指针异常是导致Java应用程序失败的最常见原因.以前,为了解决空指针异常,Google公司著名的Guava项目引入了Optional类,Guava通过使用检查空值的方式来防止代码污染,它鼓励程序员写 ...
- 如何更好地使用Java 8的Optional
Java 8中的Optional<T> 是一个可以包含或不可以包含非空值的容器对象,在 Stream API中很多地方也都使用到了Optional. java中非常讨厌的一点就是nullp ...
- Tomcat上java.lang.IllegalStateException: Optional int parameter 'id' is not present
今日, 本人在tomcat+spring mvc平台的服务器上遇到java.lang.IllegalStateException: Optional int parameter 'id' is not ...
随机推荐
- UVA 437_The Tower of Babylon
题意: 一堆石头,给定长宽高,每种石头均可以使用无数次,问这堆石头可以叠放的最高高度,要求下面的石头的长和宽分别严格大于上面石头的长和宽. 分析: 采用DAG最长路算法,由于长宽较大,不能直接用于表示 ...
- zoj2853 Evolution
给定一个进化的矩阵图,问在m次之后最终的物种有多少个,实际上这和线性代数及其应用里的一个例题是一样的...总之就相当于煞笔的套个矩阵不断去乘m次,然后每次都会根据得到进化后各物种的个数,矩阵快速幂求一 ...
- POJ2586 Y2K Accounting Bug 解题报告
Description Accounting for Computer Machinists (ACM) has sufferred from the Y2K bug and lost some vi ...
- 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 ...
- java web 验证码 第一次不正确的问题,解决方案
首先是form表单 ,获取图片验证码 然后使用js 去服务器验证 问题: 第一次明明输入正确 ,确验证不了??那是因为你在form表单发起请求 和 ajax 发起的请求 地址 中 一个使用127. ...
- react 项目实战(二)创建 用户添加 页面 及 fetch请求 json-server db.json -w -p 8000
1.安装 路由 npm install -S react-router@3.x 2.新增页面 我们现在的应用只有一个Hello React的页面,现在需要添加一个用于添加用户的页面. 首先在/src目 ...
- Objective-C之成魔之路【8-訪问成员变量和属性】
郝萌主倾心贡献,尊重作者的劳动成果.请勿转载. 假设文章对您有所帮助,欢迎给作者捐赠,支持郝萌主,捐赠数额任意,重在心意^_^ 我要捐赠: 点击捐赠 Cocos2d-X源代码下载:点我传送 訪问成员变 ...
- UTF-8 的中文檔案名上傳問題
在上传文件后,一般都会用 move_uploaded_file() 进行文件移动改名.但是 move_uploaded_file() 并不支持 UTF-8 编码,如果含有,那么函数就会执行失败,但是这 ...
- android学习笔记NO.5
Intent 能够理解为信使 由Intent来协助完毕android各个组件之间的通讯. 感觉基础薄弱就又复习了一遍基础知识,整理了一遍! gen 保存自己主动生成的R资源目录 gen->com ...
- tflearn 保存模型重新训练
from:https://stackoverflow.com/questions/41616292/how-to-load-and-retrain-tflean-model This is to cr ...