前言

Java 8中引入了 Optional 类来解决 NullPointerException 与繁琐的 null 检查,该类首次出现在 Guava。Java 8 才成为类库中的一部分。

入门

Optional 是一个封装值的类,用于保存类型为 T 的值;本质上,Optional 就是一个容器。

举例来说,一个人可能有车也可能没有,那么 Person 类内部 car 变量就不应该声明为 Car,当变量存在时,Optional 类只是对 Car 的简单封装。变量不存在时,会使用 Optional.empty() 方法返回空的 Optional 对象。如下所示:

但是 null 引用和 Optional.empty() 有什么本质区别?从语义上,它们可以当成一回事儿,但实际上差别非常大:如果尝试解引用一个 null,一定会触发 NullPointerException,不过使用 Optional.empty() 是一个有效的对象。

下面我们来看一下 Optional 提供的功能。

创建

说到 Optional 的功能,我们首先要了解 Optional 实例的创建。

空Optional

正如前文提到,你可以通过静态工厂方法 Optional.empty,创建一个空的 Optional 对象:

Optional option = Optional.empty(); 

因为 empty() 本身代表的就是空对象,所以调用 get 方法会抛出 NoSuchElementException 异常。

非空Optional

你还可以使用静态工厂方法 Optional.of ,依据一个非空值创建一个 Optional 对象:

Optional optional = Optional.of(car); 

如果 car 是一个 null,这段代码会立即抛出一个 NullPointerException,而不是等到你试图访问 car 的属性值时才返回一个错误。

可为null的Optional

最后,使用静态工厂方法 Optional.ofNullable,你可以创建一个允许 null 值的 Optional 对象:

Optional optional = Optional.ofNullable(car); 

如果 carnull,那么得到的 Optional 对象就是个空对象。我们可以查看一下它的实现原理:

public static  Optional ofNullable(T value) {
	return value == null ? empty() : of(value);
} 

根据它的实现方式,我们可知,传入的值是空值时,会返回 Optional.empty() 空对象。这有利于我们封装那些可能为 null 的值。例如,有一个 Map 实例,访问 key 索引时,如果没有与 key 关联的值,则会返回一个 null。因此,我们可以使用 Optional.ofNullable 方法封装返回值:

Optional
	
		value = Optional.ofNullable(map.get("key"));
// 可以由上代码代替 Object value = map.get("key");
		

这样可以将潜在的 null 隐患替换为空的 Optional 对象。

操作

我们创建了 Optional 实例后,需要对该实例进行操作。

isPresent & get

Optional 类中,isPresent 方法对 Optional 实例进行判断,是否包含值,如果存在值,就返回 true,否则返回 false;与之相对的是 isEmpty 方法Optional 类中还有 get 方法,它是用来获取 Optional 实例中的值。

Optional optional = Optional.of("is present");
if (optional.isPresent()) {
	System.out.println("the value is " + optional.get());
} 

isPresentget 一般组合使用来避免 NullPointerException

public boolean isPresent() {
	return value != null;
}
public T get() {
	if (value == null) {
		throw new NoSuchElementException("No value present");
	}
	return value;
} 

从源码中可看出,get 方法在取值时,要进行判空操作,如果不使用 isPresent 方法,可能会出现空指针异常。但是这种方式和在代码中if(null != value) 没有区别,因此我们要尽量避免使用该组合。

ifPresent

除了 isPresent 的简洁方法,Optional 还提供了接收函数式参数的接口 ifPresent

public void ifPresent(Consumer action) {
	if (value != null) {
		action.accept(value);
	}
} 

该方法会接收一个消费型函数。如果 Optional 实例中的值不为空,则调用 Consumeraccept 方法对 value 进行消费,若为空则不做处理。上面的例子可以使用 ifPresent 重写:

Optional optional = Optional.of("is present");
optional.isPresent((val) -> System.out.println("the value is " + val)); 

orElse

我们还可以使用 orElse 方法读取 Optional 中的值。

public T orElse(T other) {
	return value != null ? value : other;
} 

使用这种方式可以定义一个默认值,这种方式当遭遇 Optional 中的变量为空时,默认值会作为该方法的返回值。

String optGet = null;
String orElse = Optional.ofNullable(optGet).orElse("Default");
/**
 * 输出结果:
 * Default
 */ 

orElseGet

如果该方法不够,我们可以使用另一种方式 orElseGet

public T orElseGet(Supplier supplier) {
	return value != null ? value : supplier.get();
} 

该方法与 orElse 的区别就是值不存在时,调用实现 Supplier 接口的方法或Lambda表达式来返回默认值。

String optGet = null;
String orElse = Optional.ofNullable(optGet).orElse(() -> "Default");
/**
 * 输出结果:
 * Default
 */ 

orElseThrow

orElseThrow方法是在有值时返回其值,无值的时候会抛出由 Supplier 创建的异常。我们看一下它的实现原理:

public  T orElseThrow(Supplier exceptionSupplier) throws X {
	if (value != null) {
		return value;
	} else {
		throw exceptionSupplier.get();
	}
} 

orElseThrow 原理中,会传入一个Lambda表达式或方法,如果值不存在来抛出异常:

class NoValueException extends RuntimeException {
    public NoValueException() {
        super();
    }
    @Override
    public String getMessage() {
        return "No value present in the Optional instance";
    }
}
public static Integer orElseThrow() {
	return (Integer) Optional.empty().orElseThrow(NoValueException::new);
}
public static void main(String[] args) {
	orElseThrow();
}
/**
 * 控制台会抛出异常:
 * Exception in thread "main" xx.NoValueException: No value present in the Optional instance
 */ 

orElseThroworElseGet 的区别就是一个在无值的时候抛出异常,一个在无值的时候使用Lambda表达式来实现默认值。

orElseThrow 只是在无值的时候抛出异常,那本身会抛出异常的方法呢?

现在,我们拿 Integer.parseInt(String) 做个例子:

public static Integer toInt(String s) {
	try {
		// 如果String能转换为对应的Integer,将其封装在Optional对象中返回
		return Integer.parseInt(s);
	} catch (NumberFormatException e) {
		return null;	// 返回null 或者抛出异常
	}
} 

在将 String 转换为 int 时,如果无法解析到对应的整型,该方法会抛出 NumberFormatException 异常。我们在该方法中使用 try/catch 语句捕获了该异常,不能使用 if 条件判断来控制一个变量的值是否为空。

这时,我们可以使用 Optional 类,来对无法转换的 String 时返回的非法值进行建模,因此,我们可以对上述方法进行改进:

public static Optional toInt(String s) {
	try {
		// 如果String能转换为对应的Integer,将其封装在Optional对象中返回
		return Optional.of(Integer.parseInt(s));
	} catch (NumberFormatException e) {
		return Optional.empty();	// 否则返回一个空的 Optional 对象
	}
} 

这种返回 Optional 的方式适用很多方法,我们只需要获取被 Optional 包装的值的实例即可。

map

Optional 提供了 map 方法用于从对象中提取信息,它的工作原理如下:

public Optional map(Function mapper) {
	Objects.requireNonNull(mapper);
	if (!isPresent())
		return empty();
	else {
		return Optional.ofNullable(mapper.apply(value));
	}
} 

map 操作会将提供的函数应用于流的每个元素。我们可以把 Optional 对象看成一种特殊的集合数据,它至多包含一个元素。如果 Optional 包含一个值,那通过实现了 Function 接口的 Lambda 表达式对值进行转换。如果不熟悉 Function 接口,可以参考这篇文章。map 方法示例如下:

class Car {
	private String name;
	private String type;
	...省略getter与setter...
}
Optional optional = Optional.ofNullable(car);
Optional name = optional.map(Car::getName); 

flatMap

我们可以使用 map 方法来从被 Optional 类包装的 Person 类中获取 Car 的名称:

class Person {
	private Optional car;
	public Person(Car car) {
		this.car = Optional.of(car);
	}
	...省略getter与setter...
}
Person person = new Person(new Car());
Optional optPerson = Optional.of(person);
Optional name = optPerson.map(Person::getCar)
	.map(Car::getName); 

不幸的是,这段代码无法通过编译。为什么呢?optPersonOptional 类型的变量,调用 map 方法应该没有问题。但 getCar 返回的是一个 Optional 类型的对象,这意味着 map 操作的结果是一个 Optional> 类型的对象。因此,它对 getName 的调用是非法的,因为最外层的 optional 对象包含了另一个 optional 对象的值,而它当然不会支持 getName 方法。

所以,我们使用 flatMap 方法。该方法接受一个函数作为参数,这个函数的返回值是另一个流。

public  Optional flatMap(Function> mapper) {
	Objects.requireNonNull(mapper);
	if (!isPresent()) {
		return empty();
	} else {
		@SuppressWarnings("unchecked")
		Optional r = (Optional) mapper.apply(value);
		return Objects.requireNonNull(r);
	}
} 

参照 map 函数,使用 flatMap 重写上述的示例:

Optional name = optPerson.flatMap(Person::getCar).map(Car::getName); 

filter

有时候我们需要对 Optional 中的值进行过滤,获得我们需要的结果,我们就可以使用 filter 方法:

public Optional filter(Predicate predicate) {
	Objects.requireNonNull(predicate);
	if (!isPresent())
		return this;
	else
		return predicate.test(value) ? this : empty();
} 

该方法接受 Predicate 谓词作为参数。如果 Optional 对象的值存在,并且符合谓词的条件,即操作结果为truefilter 方法不做任何改变并返回其值;否则就将该值过滤掉并返回一个空的 Optional 对象。

Optional optionalS = Optional.of("13846901234");
optionalS = optionalS.filter(s -> s.contains("138"));
/**
 * 上述 `filter` 方法满足条件可以返回同一个Optional,否则返回空Optional
 */ 

总结

Java 8引入的 java.util.Optional 让我们以函数式编程的方式处理 null,防止空指针异常;并支持多种方式用于操作值,比如:mapflatMapfilter,这样可以抛弃嵌套的 if-else 代码块,设计更好的 API,代码的可读性也大大提高,但是如果在域模型中使用 Optional,由于没有实现 Serializable 接口,不能进行实例化,也不能作为类的字段。

更多内容请关注公众号「海人的博客」,回复「资源」即可获得免费学习资源!

标签:

讲讲Java8的Optional类的更多相关文章

  1. springboot整合websocket最基础入门使用教程详解

    项目最终的文件结构1 添加maven依赖<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artif......

  2. Ajax实现局部刷新的方法实例

    前言 最近复习了一下jQuery的一些内容,特此整理一下一些能用的得到的知识点,以前才学jQuery的时候压根就没有注意到那么多的细节,另外最近一直都在整理前端的一些工作中学到的小经验,大概还会有十篇左右的内容,就会慢慢开始整理后端,框架,以及数据库的一些小知识点 一、 Ajax是什么? 概念: A......

  3. Java 添加数字签名到Excel以及检测、删除签名

    Excel中可添加数字签名以供文档所有者申明文档的所有权或有效性。文本以Java代码示例介绍如何在Excel文档中对数字签名功能进行相关操作,包括如何添加签名到Excel、检测Excel文档是否已签名,以及如何删除Excel文档中的签名。本次代码测试环境Excel版本:2013编译环境:Intell......

  4. 如何讲清楚 Java 面向对象的问题与知识?(类与对象,封装,继承,多态,接口,内部类...)

    如何讲清楚 Java 面向对象的问题与知识?(类与对象,封装,继承,多态,接口,内部类...)写在最前面这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解。所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项目,专注......

  5. [Java基础]——String类

    此篇博客主要整理Java中的String类的使用。一、String1.1 String 的定义上图是jdk中对String类的定义,得到的信息有:①、String类声明为final的,不可被继承。②、String类实现了Serializable接口,表示字符串是支持序列化的(IO流中使用)。实......

  6. Java并发/多线程-CAS原理分析

    目录什么是CAS并发安全问题举一个典型的例子i++如何解决?底层原理CAS需要注意的问题使用限制ABA 问题概念解决方案高竞争下的开销问题什么是CASCAS 即 compare and swap,比较并交换。CAS是一种原子操作,同时 CAS 使用乐观锁机制。J.U.C中的很多功能都是建立在 CAS......

  7. java 利用HttpClient PostMethod提交json数据操作

    故事前要今天,在做公司的一个项目,需要和第三方公司进行对接,需要将我们采集到的数据发送给第三方公司,按照对方提供的文档,传递好参数后,httpclient.execute(method)请求后,得到的状态码 ,一直是502,犹豫第一次使用HttpClient post json数据,一直怀疑是自己的......

  8. Java 找不到或无法加载主类的修复方法

    有时,当我们运行Java程序时,我们可能会看到“找不到或无法加载主类”。原因很容易猜测:JVM找不到主类并给出了这个错误。但是为什么不能呢?在本文中,我们将讨论找不到主类的可能原因。另外,我们将看看如何修复它们。示例程序我们将从HelloWorld程序开始:public class HelloWor......

  9. java编译命令基础知识点

    我们在对计算机下达指令时,人类的语言它是不能够明白,需要通过编译的时候翻译成计算机能听懂的语言。编译过程中会调用javac命令,这点大家可能接触的不多,毕竟是是计算机程序内部运行时的操作。下面我们就编译的概念、命令带来讲解,然后分享一个编译实例给大家练习。1.编译概念通过流程图可以看出其实java的......

  10. 详解java中static关键词的作用

    在java中,static是一个修饰符,用于修饰类的成员方法、类的成员变量,另外可以编写static代码块来优化程序性能;被static关键字修饰的方法或者变量不需要依赖于对象来进行访问,只要类被加载了,就可以通过类名去进行访问。static关键词的作用1、静态成员变量的语法特定2、静态函数的语法特......

随机推荐

  1. 基于C#的百度图片批量下载工具

    using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using Sys......

  2. python生成二维码

    python生成二维码需要用到的包pip install qrcode 代码:import qrcode from PIL import Image # 如果需要在二维码中添加图片logo需要 # 模块导入 data = "www.baidu.com" img_name = '二......

  3. c#发送请求访问外部接口的实例

    我就废话不多说了,大家还是直接看代码吧~string url = "https://cloud.soei.com.cn/smsapi/sms/verifycode";HttpClient httpClient = new HttpClient();httpClient.BaseA......

  4. pytorch 实现冻结部分参数训练另一部分

    1)添加下面一句话到模型中for p in self.parameters():p.requires_grad = False比如加载了resnet预训练模型之后,在resenet的基础上连接了新的模快,resenet模块那部分可以先暂时冻结不更新,只更新其他部分的参数,那么可以在下面加入上面那句话......

  5. 在nodejs中创建cluster

    目录简介cluster集群cluster详解cluster中的eventcluster中的方法cluster中的属性cluster中的worker总结在nodejs中创建cluster简介在前面的文章中,我们讲到了可以通过worker_threads来创建新的线程,可以使用child_process......

  6. python工具系列-弱口令工具

    python弱口令扫描工具-初始版本篇博客记录下基于python写的gui小工具,也是我学python以来自己动手写的第一款交互型的工具Tkinter: Tkinter 模块(Tk 接口)是 Python 的标准 Tk GUI 工具包的接口 .Tk 和 Tkinter可以在大多数的 Unix 平台下......

  7. 详解如何在C#中使用投影(Projection)

    投影(Projection) 是一种可以将查询结果进行 塑性 的一种操作,你可以使用 投影 将一个 object 转成仅包含你需要属性的新对象,这篇文章中,我们就一起看看如何使用 投影 功能。C# 中的投影LINQ 集成查询中有两个支持投影的扩展方法,分别为: Select 和 SelectMany......

  8. 虚函数表-C++多态的实现原理解析

    参考:http://c.biancheng.net/view/267.html1、说明我们都知道多态指的是父类的指针在运行中指向子类,那么它的实现原理是什么呢?答案是虚函数表在 关于virtual 一文中,我们详细了解了C++多态的使用方式,我们知道没有 virtual 关键子就没法使用多态2、虚函......

  9. python中re模块的使用(正则表达式)

    一、什么是正则表达式?正则表达式,又称规则表达式,通常被用来检索、替换那些符合某个模式(规则)的文本。正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。二、正则表达式的匹配规则1.表......

  10. vue 中 get / delete 传递数组参数方法

    在前后端交互的时候,有时候需要通过 get 或者 delete 传递一个数组给后台,但是这样直接传递后台无法接收数据,因为在传递的过程中数组参数会被转译,结果如下:参数:{ name : [ 1, 2, 3 ] }转译效果:http://aaa.com?name[]=1&name[]=2&a......