Optional类介绍
从 Java 8
引入的一个很有趣的特性是 Optional 类。Optional 类主要解决的问题是臭名昭著的空指针异常(NullPointerException
) —— 每个 Java 程序员都非常了解的异常。
本质上,这是一个包含有可选值的包装类,这意味着 Optional 类既可以含有对象也可以为空。
Optional 是 Java 实现函数式编程的强劲一步,并且帮助在范式中实现。但是 Optional 的意义显然不止于此。
我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException
:
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
在这个小示例中,如果我们需要确保不触发异常,就得在访问每一个值之前对其进行明确地检查:
// 查询国家码
public void getIsoCode(User user) {
if (user != null) {
Address address = user.getAddress();
if (address != null) {
Country country = address.getCountry();
if (country != null) {
String isocode = country.getIsocode();
if (isocode != null) {
isocode = isocode.toUpperCase();
}
}
}
}
}
你看到了,这很容易就变得冗长,难以维护。
为了简化这个过程,我们来看看用 Optional 类是怎么做的。从创建和验证实例,到使用其不同的方法,并与其它返回相同类型的方法相结合,下面是见证 Optional 奇迹的时刻。
public String getIsoCode(User user) {
return Optional.ofNullable(user)
.map(a -> a.getAddress())
.map(a -> a.getCountry())
.map(a -> a.getIsocode())
.map(a -> a.toUpperCase())
.orElse("CHINA");
}
Optional类的使用
Optional类有很多方法,通过of()
、ofNullable()
构造Optional实例,Optional类拥有value属性
,通过get()
获取实例的value属性值,通过组合这些方法,最终实现上述解决NullPointerException问题的。下面我们来认识一些基础的方法,逐步揭秘。
创建Optional实例
Optional.empty()
empty()方法返回一个空的Optional对象,其value属性
为null
,用法如下。
Optional<User> emptyOpt = Optional.empty();
empty()方法之所以放在第一个介绍是有原因的,因为涉及到Optional的构造是私有的
,这个知识很重要,我们来看下源码:
/*
* @since 1.8 标记1.8版本后才有的
*/
public final class Optional<T> {
//内部实例化一个Optional实例,命名为EMPTY ,全局唯一的
private static final Optional<?> EMPTY = new Optional<>();
//value属性
private final T value;
//empty()方法实际上 就是返回之前那个命名为EMPTY 实例
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
//构造函数,注意是私有的,无法在外部用new访问
private Optional() {
this.value = null;
}
Optional构造方法是私有的,也就是说不允许外部通过new的形式创建对象
。构造方法是供Optional类内部使用。Optional内部维护这个一个value的变量,无参数构建的时候value为null
Optional.empty()返回的实例对象时全局唯一的,多次调用,结果是同一个对象:
@org.junit.Test
public void testEmpty() {
Assert.assertEquals(Optional.empty(), Optional.empty()); //我们调用了2次empty(),并比较equals
Assert.assertTrue(Optional.empty() == Optional.empty()); //比较对象地址
}
Optional.of()
创建value属性为指定值的 Optional实例,就是用一个Optional对象,包裹了给定的值。用法如下:
Optional<User> opt = Optional.of(user);
通过of方法所构造出的Optional对象有如下特点:
- 当指定值为空时,会报NullPointerException异常
- 当指定值不为空时,正常构造Optional对象
我们通过源码来分析下原因:
public final class Optional<T> { //Optional类
public static <T> Optional<T> of(T value) {
return new Optional<>(value); //调用私有的构造函数
}
private Optional(T value) {
this.value = Objects.requireNonNull(value); //调用Objects.requireNonNull限制指定值非空
}
}
public final class Objects { //Objects工具类
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException(); //如果为空,则抛出异常
return obj;
}
注意: 只有你确定对象100%不为 null 的时候才能使用 of(),否则会报NullPointerException
Optional.ofNullable()
创建value属性为指定值的 Optional实例,如果指定为空,则返回一个空的Optional实例。
Optional<User> opt = Optional.ofNullable(user);
ofNullable用法和of()相似,但是区别在于ofNullable对指定值无限制,即使传入一个null值,也不会报错,通过源码来分析原因:
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value); //给定值为空,返回空的Optional实例,不为空,则复用of()构造一个新的实例
}
如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法,避免用of()方法
访问 Optional 对象的值
我们在前面知道Optional就是包裹了给定的值,进行了封装,自身有个value属性
,那么获取对象的值,就是获取value属性
。
get()
从 Optional 实例中取回value属性
@Test
public void whenCreateOfNullableOptional_thenOk() {
String name = "John";
Optional<String> opt = Optional.ofNullable(name); //构建一个Optional 实例
assertEquals("John", opt.get()); //获取Optional 实例的值
}
不过,需要注意的是,当value属性为空时会报错:
@Test(expected = NoSuchElementException.class)
public void testGetThrowException() {
Optional.empty().get();
}
通过源码分析下原因:
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present"); //判断value属性为空时抛出异常
}
return value; //返回value属性
}
为了避免出错,可以在调用
get()
之前先验证是否有值,isPresent()
应运而生
isPresent()
判断value属性
值是否存在。value属性
== null 表示不存在,非null表示存在。
例子:
@org.junit.Test
public void testIsPresent() {
Optional<Integer> optional1 = Optional.ofNullable(1);
Optional<Integer> optional2 = Optional.ofNullable(null);
Assert.assertEquals(optional1.isPresent(), true); //存在
Assert.assertEquals(optional2.isPresent(), false); //不存在
}
注意:方法的
前缀是is,不是if
,避免和下面的ifPresent()混淆
由于null、空字符串比较容易混淆 “存在”的含义,我们上源码:
public boolean isPresent() {
return value != null; //与null进行比较
}
由源码得出结论:空字符串表示存在
ifPresent()
方法定义:
public void ifPresent(Consumer<? super T> consumer)
ifPresent()不是获取值,该方法没有返回值。之所以放在此处介绍,是因为名称和前面的isPresent()容易混淆。
如果option对象的value属性值不是null,则调用consumer对象,并将value属性值作为入参传入,否则不调用。Consumer可以理解为一个Lambda 表达式(Lambda 章节专门讲解Consumer用法),并且限定只有一个入参:
Optional<User> opt = Optional.ofNullable(user);
opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));
这个例子中,只有 user 用户不为 null 的时候才会执行断言。
orElse(T other)
结构为:
opt.orElse( obj);
orElse(T other)是一个工具方法,作用类似get()
,可以获取value属性
的值,之所以说是一个工具方法,是因为当value属性值为空时,返回的值是指定的other参数值:
@org.junit.Test
public void testOrElse() {
Optional<String> optional1 = Optional.ofNullable("zhangsan"); //option实例非空
Optional<String> optional2 = Optional.ofNullable(null); //option实例为空
Assert.assertEquals(optional1.orElse("lisi"), "zhangsan"); //option实例非空,返回value属性值
Assert.assertEquals(optional2.orElse("lisi"), "lisi"); //option实例为空,则返回指定的新值
}
源码也很简单:
public T orElse(T other) {
return value != null ? value : other; //value不为空,则返回value;为空则返回other
}
这个方法很重要,比如我们经常在处理返回值时,如果值是一个null,我们希望转化为空字符串,这样返回值在外部被调用时就不会报空指针了
public String getValue(){ //常规方式
String result = doSomething();
if(result==null){
return ""; //把null转化为空字符串
}
}
public String getValue(){ //Optional方式
String result = doSomething();
return Optional.of(result).orElse(""); //自动转化为空字符串
}
orElseGet(Supplier<? extends T> other)
用法为:
opt.orElseGet( () -> xxx);
从字面上就能看出作用与orElse()
类似,也是获取value属性
值,但行为略有不同。这个方法会在value属有值的时候返回value属值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口
,并将返回其执行结果:
@org.junit.Test
public void testOrElseGet() {
Optional<Integer> optional1 = Optional.ofNullable(1); //option实例非空,并且value属性值为1
Optional<Integer> optional2 = Optional.ofNullable(null); //option实例为空
Assert.assertEquals(optional1.orElseGet(() -> {
return Integer.valueOf(1000); //表达式,执行结果为1000
}), Integer.valueOf(1)); //option实例非空,返回value属性值1
Assert.assertEquals(optional2.orElseGet(() -> {
return Integer.valueOf(1000); //表达式,执行结果为1000
}), Integer.valueOf(1000)); //option实例为空,则返回Supplier Lambda结果,1000
}
orElse() 和 orElseGet() 的不同之处
orElse() 和 orElseGet() 的不同之处在于前者传入一个值,后者是个Lambda表达式。然而,如果传入值的方式是通过调用其他方法形式就会产生一点细微区别,我们来解释下什么是调用其他方法形式传值:
public class Test {
public String functionA() { //方法A
return ..;
}
public void functionB(){
Optional opt = ...
opt.orElse(functionA()) //orElse()调用方法A传入值
}
public void functionC(){
Optional opt = ...
opt.orElseGet(()-> functionA()) //orElseGet调用方法A传入值
}
}
区别在与,如果传入的value不为空,orElse()中的方法仍然要执行,而orElseGet() 不执行。
测试value属性为空情况,二者都会执行:
@org.junit.Test
public void testOrElseAndOrElseGet() {
Optional<String> optional1 = Optional.ofNullable(null); //value属性为空
Optional<String> optional2 = Optional.ofNullable(null); //value属性为空
optional1.orElse(getName()); //会执行getName()
System.out.println("--------------");
optional2.orElseGet(() -> getName()); //会执行getName()
}
public String getName() {
System.out.println("getName() executed !"); //打印执行标识
return "lisi";
}
我们看下输出:
getName() executed ! //orElse触发打印的
--------------
getName() executed ! //orElseGet触发打印的
测试value属性不为空情况,orElse会执行,orElseGet不执行:
@org.junit.Test
public void testOrElseAndOrElseGet2() {
Optional<String> optional1 = Optional.ofNullable("lisi"); //value属性不为空
Optional<String> optional2 = Optional.ofNullable("lisi"); //value属性不为空
optional1.orElse(getName()); //会执行getName()
System.out.println("--------------");
optional2.orElseGet(() -> getName()); //不会执行getName()
}
我们看下输出:
getName() executed !
--------------
在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。
orElseThrow(Supplier<? extends X> exceptionSupplier)
它会在对象为空的时候抛出异常,而不是返回某个值,并且该异常由入参传入:
@Test(expected = IllegalArgumentException.class) //期望抛IllegalArgumentException异常
public void testOrElseThrow() {
Optional.ofNullable(null).orElseThrow(() -> new IllegalArgumentException()); // 我们定义了一个空的实例,orElseThrow传入一个异常
}
这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。
过滤值
filter(Predicate<? super T> predicate)
filter()方法作用是当Optional中value属性不为空的时,对Optional中的值进行过滤。
注意:filter()方法返回值仍是一个Optional对象,并且对象value属性可能为空,而不是一个具体的值。
filter() 接受一个 Predicate 参数,这是个判断表达式
,判断结果为true时,保留当前实例,判断结果为false时,返回一个空的Optional。
@org.junit.Test
public void testFilter() {
Optional<String> optional1 = Optional.ofNullable("abcd"); //非空实例
Optional<String> optional2 = Optional.ofNullable(null); //空实例
//非空实例场景
Assert.assertEquals(optional1.filter(a -> a.startsWith("ab")).get(), "abcd"); //表达式结果为true,保留当前实例
Assert.assertEquals(optional1.filter(a -> a.startsWith("AB")), Optional.empty()); //表达式结果为fasle,返回空实例
//空实例场景,直接返回空对象
Assert.assertEquals(optional2.filter((a) -> (1 == 1)), Optional.empty());
}
转换值
map()和flatMap()对Optional中的对象进行转换值的操作,这两个方法唯一的区别就是接受的参数不同。
map()
public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
对Optional中保存的值进行函数运算,并返回新的Optional(可以是任何类型)。入参是一个mapping函数,Optional的value属性作为mapping函数入参。
注意:map()返回值是Optional对象,mapper函数返回值是任意值,最终会被封装成Optional对象
我们看个例子:
@org.junit.Test
public void testMap() {
Optional<String> optional1 = Optional.ofNullable("ab"); // 非空实例
Optional<String> optional2 = Optional.ofNullable(null); //空实例
Assert.assertEquals(optional1.map(a -> a + "cd").get(), "abcd"); //函数作用是拼接字符串
Assert.assertEquals(optional2.map(a -> a + "cd"), Optional.empty()); //空实例返回空实例,不会调用函数表达式
}
看下源码:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper); //传入的函数必须存在,不能是null
if (!isPresent())
return empty(); //如果属性值为空,直接返回
else {
return Optional.ofNullable(mapper.apply(value)); //计算后的值,如果为空,通过ofNullable返回empty()
}
}
由源码,总结特点:
- 传入的函数不能为null,否则报NullPointerException
Objects.requireNonNull(mapper)起的作用 - 如果实例本身为空,不会执行函数,直接返回空
- 如果执行函数的计算结果为null,会返回empty()
对mapping返回值调用ofNullable封装起的作用 - mapper函数返回值是任意值,最终会被封装成Optional对象
对mapping返回值调用ofNullable封装起的作用
flatMap()
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper)
flatMap方法与map方法类似,也是对Optional中保存的值进行函数运算,区别在于mapping
函数的返回值不同。
map()
方法的mapping
函数返回值可以是任何类型T
,而flatMap()
方法的mapping
函数必须是Optional
。
我们来看个例子,这个例子和map()非常相似,只是对mapping返回值进行封装:
@org.junit.Test
public void testFlatMap() {
Optional<String> optional1 = Optional.ofNullable("ab"); //构建一个非空实例
Optional<String> optional2 = Optional.ofNullable(null); // 构建一个空实例
Assert.assertEquals(optional1.flatMap(a -> Optional.of(a + "cd")).get(), "abcd"); //接收一个mapping函数,进行拼接,并手动封装成optional实例
Assert.assertEquals(optional2.flatMap(a -> Optional.of(a + "cd")), Optional.empty()); //空实例直接返回空实例
}
验证mapping返回值不能为null:
@org.junit.Test(expected = NullPointerException.class) //期望抛出异常
public void testFlatMap_ReturnNotNull() {
Optional<String> optional1 = Optional.ofNullable("ab");
optional1.flatMap(a -> {
return null; //故意返回null
});
}
看下源码:
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty(); //自身为空,直接返回空Optional
else {
return Objects.requireNonNull(mapper.apply(value)); //mapping 返回结果不能是null,可以是一个空的Optional实例
//由于mapping结果未经ofNullable封装,因此需要mapping自身封装一个Optional对象
}
}
由源码,总结特点:
- mapping返回值是个Option对象
flatMap最终返回值是一个Option对象,由于mapping结果未经ofNullable封装,因此需要mapping自身封装一个Option对象。这是与map()最大区别的根本原因。 - mapping返回值不能是null
由于mapping结果返回值被Objects.requireNonNull检查,因此格外注意返回值一定不能是null,可以返回一个空的Optional
- 如果实例本身为空,不会执行函数,直接返回空Optional
Optional 类的最佳实践
经过上面的铺垫,我们再来回顾开篇的问题,为何可以简便的解决空指针问题?
User.java
public class User {
private Address address;//省略get,set方法
Address.java
public class Address {
private Country country; //省略get,set方法
Country.java
public class Country {
private String isocode;//省略get,set方法
TestOption.java
public String getIsoCode2(User user) {
return Optional.ofNullable(user)
.map(a -> a.getAddress())
.map(a -> a.getCountry())
.map(a -> a.getIsocode())
.map(a -> a.toUpperCase())
.orElse("CHINA");
}
@Test
public void test() {
//#1 user为null,直接越过中间步骤,根据orElse,得到默认值
User user = null;
Assert.assertEquals(getIsoCode(user), "CHINA");
//#2 user非null,但是address属性是null,直接越过中间步骤,根据orElse,得到默认值
user = new User();
Assert.assertEquals(getIsoCode(user), "CHINA");
//#3 address属性非null,但是country属性是null,根据orElse,得到默认值
Address address = new Address();
user.setAddress(address);
Assert.assertEquals(getIsoCode(user), "CHINA");
//#4 country非null,那么最终结果就是其值
Country country = new Country();
country.setIsocode("Usa");
address.setCountry(country);
Assert.assertEquals(getIsoCode(user), "USA");
}
我们来分析一下上面的单元测试 #1步骤:
- 执行 Optional.ofNullable(user)
ofNullable对user进行封装,得到一个Optional.empty实例 - 执行 .map(a -> a.getAddress())
由于此时Optional对象自身为空实例,map会直接返回空实例 - 执行.map(a -> a.getCountry())
由于此时Optional对象自身为空实例,map会直接返回空实例 - 执行 .map(a -> a.getIsocode())
由于此时Optional对象自身为空实例,map会直接返回空实例 - 执行 .map(a -> a.toUpperCase())
由于此时Optional对象自身为空实例,map会直接返回空实例 - 执行 .orElse(“CHINA”);
orElse作用是返回一个值,这个是最终值,传给return 语句,由于此时Optional对象自身为空实例,orElse返回给定的默认值,即CHINA
。
简单来说,通过map,把本来为null的属性转化为一个Optional.empty实例,在链式写法中,空实例调用方法map()不会报错,并且继续获的一个Optional.empty实例,直至链的尾部,通过orElse()收尾。
如果觉得对您有帮助,请不要吝惜您点赞和留言,呵呵,写的蛮辛苦的。