Optional方法解释以及使用
Optional
1、介绍
先来看看 Optional 设计出来的意图是什么, Java 语言架构师 Brian Goetz 是这么说的:
Optional 可以给返回结果提供了一个表示无结果的值,而不是返回 null,其实是一个空的Optional的空实例对象。
Optional 其实就是一个箱子,里面存储着物品。那么这个箱子面可以存放的有东西,可能存放的也没有东西。
Optional可以很方便的帮助我们来解决NPE的问题,但是并非是说一定不会发生NPE问题,当不小心使用的时候也还是会发生的。Optional类通过一些操作封装,表现出来好像用 Optional 就不需要关心空指针的情况。
而事实上是 Optional 在替我们负重前行,该有的判断它替我们完成了,而且用了 Optional 最后拿结果的时候还是小心的,盲目 get 一样会抛错,Brian Goetz 说 get 应该叫 getOrElseThrowNoSuchElementException。
来一个案例来进行演示:
Yes yes = getYes();
if (yes != null) {
Address yesAddress = yes.getAddress();
if (yesAddress != null) {
Province province = yesAddress.getProvince();
System.out.println(province.getName());
}
}
throw new NoSuchElementException(); //如果没找到就抛错
对上面的代码进行分析的话,如果yes为null,那么就直接抛出没有元素异常;如果存在,那么获取得到地址;如果地址也存在,那么获取得到市等等操作。将上面这段话翻译成Optional的使用就是下面这种方式
如果用 Optional 的话,那就变成下面这样:
Optional.ofNullable(getYes())
.map(a -> a.getAddress())
.map(p -> p.getProvince())
.map(n -> n.getName())
.orElseThrow(NoSuchElementException::new);
可以看到,如果用了 Optional,代码里不需要判空的操作,即使 address 、province 为空的话,也不会产生空指针错误,这就是 Optional 带来的好处!
说到这,我想提个问:
如果在 a.getAddress()
时拿不到值的话,你说是会继续执行map(p -> p.getProvince())
还是直接跳到 orElseThrow
? 或者反过来如果 map(n -> n.getName())
不为空,你说 orElseThrow
这个方法会不会执行?
答案是否定的。即使ofNullable方法中的值是null,也还是会执行map方法
看下源码来解释一下上面代码执行的流程:
private static final Optional<?> EMPTY = new Optional<>();
private final T value;
// 私有空参构造,对value赋值为null
private Optional() {
this.value = null;
}
// 私有构造,value为null,抛出异常;不为null,那么返回不是EMPTY的对象实例
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
public static<T> Optional<T> empty() {
// 返回一个Optional类的EMPTY对象
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
前面提到的:Optional 就是个箱子,里面的 value 才是正主,并且内置了一个 EMPTY 对象,用来替换当 value 为 null 时候的箱子。
也就是说当箱子里面什么都没有的时候,那么箱子里面装的是EMPTY对象;如果有值的时候,装的是实际的对象;
现在看下上面演示的 map 方法,看看它的内部实现是如何让我们不需要做非空判断的。
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
// 有值返回true,没有值返回false。这里没有值就返回EMPTY实例
if (!isPresent())
return empty();
else {
// 有值的时候,这里会再次来进行判断。对接口中apply方法实现自定义得到想要的值。返回的是Optional类对象实例
return Optional.ofNullable(mapper.apply(value));
}
}
这里的mapper也就是我们是否传入了对应接口实现类的对象,如果value是空的,那么就直接返回一个Optional空实例对象;如果不是空的,那么调用apply中提供的方法,不管value是否是空,可以看到返回的都是Optional类实例,value是由Optional类来进行绑定的。可以看到上面的方法,map都是会执行的。
看完了这些源码,那么再对上面的案例进行解释一波:
如果ofNullable函数返回的是一个EMPTY对象,那么调用了map函数之后,依然返回的是一个EMPTY对象;不是EMPTY对象的时候,那么就一定是有值的,那么就会返回一个转换后的有值的被Optional包裹的对象。那么来进行map的时候,发现不是一个空的,那么久会进行转换,得到另外的值,但是事实上,获取得到返回的是一个value=null的引用而已,然后如果value继续为null,那么将会导致继续返回的是一个EMPTY对象,然后继续操作,直到最终返回的是一个得到指定的值。
再看下下面两个函数of和ofNullable:
// 直接只用Optional.of(value)来传入一个值,通过上面的构造函数可以看到,如果value是空的,那么直接NPE;如果非空,那么返回一个Optional类对象
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
ofNullable和上面的使用上非常相似,而且在里面还调用了上面的方法:
public static <T> Optional<T> ofNullable(T value) {
// 如果是null,那么就直接返回EMPTY对象;如果不是,那么就返回一个Optional类的非实例对象
return value == null ? empty() : of(value);
}
二者的区别是什么?第一个of函数获取得到的如果没有报错的情况下,那么返回的一定是一个非空的Optional对象;第二个如果value是null,那么直接返回一个EMPTY实例对象;否则就返回非空的Optional对象。
也就是说ofNullable也可以返回EMPTY对象,也可以返回非EMPTY对象。第二种的使用方式还是很多的。
2、方法使用详细介绍
Optional类的方法
方法 | 描述 |
---|---|
empty | 返回一个空的Optional类实例 |
of | 将对象封装到Optional类中去,要求对象不能够为空,否则返回一个NullPointerException |
ofNullable | 获取得到Optional类封装的对象,如果对象为空,那么返回一个空实例,如果不为空,返回一个封装了对象的Optional实例 |
filter | 如果值存在而且能够满足提供的谓词,就返回包含该值的Optional对象,否则返回一个空的Optional类实例 |
map | 如果Optional类封装的值存在,那么执行map函数中定义的内容 |
flatMap | 如果该值存在,通过Function函数,返回一个Optional类型的值,否则返回一个空的Optional类实例 |
get | 如果值存在,那么将Optional类实例封装的值返回,否则将会抛出NoSuchElementException异常 |
isPresent | 如果存在封装的对象,那么返回true;如果不存在,那么返回false |
ifPresent | 如果存在封装的对象,那么执行后面的消费方法;如果不存在,那么不做任何事情 |
orElse | 如果Optional实例对象是empty,那么使用默认的值来代替 |
orElseGet | 如果有值,则获取得到将其返回;如果没有值,那么将会使用指定的函数生成的值 |
orElseThrow | 如果有值将其进行返回,否则抛出一个指定的异常 |
2.1、创建实例的两种方式
我们从一个简单的用例开始。在 Java 8 之前,任何访问对象方法或属性的调用都可能导致 NullPointerException
String isocode = user.getAddress().getCountry().getIsocode().toUpperCase();
那么在这样子来进行使用之前,需要注意的是可以会出现空指针异常,那么为了避免空指针异常,所以应该来进行判空操作:
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类实例
@Test(expected = NoSuchElementException.class)public void whenCreateEmptyOptional_thenNull() { Optional<User> emptyOpt = Optional.empty(); emptyOpt.get();}
这种方法会来进行尝试访问 emptyOpt 变量的值会导致 NoSuchElementException。
你可以使用 of() 和 ofNullable() 方法创建包含值的 Optional。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出 NullPointerException:
@Test(expected = NullPointerException.class)public void whenCreateOfEmptyOptional_thenNullPointerException() { Optional<User> opt = Optional.of(user);}
因此,你应该明确对象不为 null 的时候使用 of()。如果对象即可能是 null 也可能是非 null,就应该使用 ofNullable() 方法:
Optional<User> opt = Optional.ofNullable(user);
2.2、访问实例的值
访问optional实例的值
从 Optional 实例中取回实际值对象的方法之一是使用 get() 方法:
@Testpublic void whenCreateOfNullableOptional_thenOk() { String name = "John"; Optional<String> opt = Optional.ofNullable(name); assertEquals("John", opt.get());}
这个方法会在值为 null 的时候抛出异常。要避免异常,你可以选择首先验证是否有值:
但是如果是下面这种情况:
@Testpublic void whenCheckIfPresent_thenOk() { User user = new User("john@gmail.com", "1234"); Optional<User> opt = Optional.ofNullable(user); assertTrue(opt.isPresent()); assertEquals(user.getEmail(), opt.get().getEmail());}
检查是否有值的另一个选择是 ifPresent() 方法。该方法除了执行检查,还接受一个Consumer(消费者) 参数,如果对象不是空的,就对执行传入的 Lambda 表达式:
opt.ifPresent( u -> assertEquals(user.getEmail(), u.getEmail()));
这个例子中,只有 user 用户不为 null 的时候才会执行断言。
2.3、默认值
接下来,我们来看看提供空值的方法。
Optional 类提供了 API 用以返回对象值,或者在对象为空的时候返回默认值。
这里你可以使用的第一个方法是 orElse(),它的工作方式非常直接,如果有值则返回该值,否则返回传递给它的参数值:
@Testpublic void whenEmptyValue_thenReturnDefault() { User user = null; User user2 = new User("anna@gmail.com", "1234"); User result = Optional.ofNullable(user).orElse(user2); assertEquals(user2.getEmail(), result.getEmail());}
这里 user 对象是空的,所以返回了作为默认值的 user2。
如果对象的初始值不是 null,那么默认值会被忽略:
@Testpublic void whenValueNotNull_thenIgnoreDefault() { User user = new User("john@gmail.com","1234"); User user2 = new User("anna@gmail.com", "1234"); User result = Optional.ofNullable(user).orElse(user2); assertEquals("john@gmail.com", result.getEmail());}
第二个同类型的 API 是 orElseGet() —— 其行为略有不同。这个方法会在有值的时候返回值,如果没有值,它会执行作为参数传入的 Supplier(供应者) 函数式接口,并将返回其执行结果:
User result = Optional.ofNullable(user).orElseGet( () -> user2);
orElse() 和 orElseGet() 的不同之处
乍一看,这两种方法似乎起着同样的作用。然而事实并非如此。我们创建一些示例来突出二者行为上的异同。
我们先来看看对象为空时他们的行为:
@Test
public void givenEmptyValue_whenCompare_thenOk() {
User user = null
logger.debug("Using orElse");
User result = Optional.ofNullable(user).orElse(createNewUser());
logger.debug("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}
private User createNewUser() {
logger.debug("Creating New User");
return new User("extra@gmail.com", "1234");
}
查看控制台输出:
Using orElse
Creating New User
Using orElseGet
Creating New User
由此可见,当对象为空而返回默认对象时,行为并无差异。
我们接下来看一个类似的示例,但这里 Optional 不为空:
@Test
public void givenPresentValue_whenCompare_thenOk() {
User user = new User("john@gmail.com", "1234");
logger.info("Using orElse");
User result = Optional.ofNullable(user).orElse(createNewUser());
logger.info("Using orElseGet");
User result2 = Optional.ofNullable(user).orElseGet(() -> createNewUser());
}
查看控制台输出:
Using orElse
Creating New User
Using orElseGet
这个示例中,两个 Optional 对象都包含非空值,两个方法都会返回对应的非空值。不过,orElse() 方法仍然创建了 User 对象。与之相反,orElseGet() 方法不创建 User对象
在执行较密集的调用时,比如调用 Web 服务或数据查询,这个差异会对性能产生重大影响。所以在使用orElse的时候推荐使用的是orElseGet方法。
不同的原因所在
我们要清楚的是方法中如果是:method(a,b());那么这种情况下,肯定是b()方法先执行的;
而如果是:method(a,()->{}); 那么这种情况就不一样了。所以这里也就是这些导致了根本性原因的地方。
那么既然在orElse中来进行使用了,那么就表明了对应的方法中一定要实现,不管是为空还是不为空,那么就应该来进行实现。
lambda表达式表示的是执行的一段逻辑,而对于普通方法来说就是正常的方法来进行执行。
2.4、返回异常
除了 orElse() 和 orElseGet() 方法,Optional 还定义了 orElseThrow() API —— 它会在对象为空的时候抛出异常,而不是返回备选的值:
@Test(expected = IllegalArgumentException.class)
public void whenThrowException_thenOk() {
User result = Optional.ofNullable(user)
.orElseThrow( () -> new IllegalArgumentException());
}
这里,如果 user 值为 null,会抛出 IllegalArgumentException。
这个方法让我们有更丰富的语义,可以决定抛出什么样的异常,而不总是抛出 NullPointerException。
现在我们已经很好地理解了如何使用 Optional,我们来看看其它可以对 Optional 值进行转换和过滤的方法。
2.5、转换值
有很多种方法可以转换 Optional 的值。我们从 map() 和 flatMap() 方法开始。
也就是说如果箱子中的东西存在,那么可以让其转换成别的东西,然后继续来进行使用。
@Test
public void whenMap_thenOk() {
User user = new User("anna@gmail.com", "1234");
String email = Optional.ofNullable(user)
.map(u -> u.getEmail()).orElse("default@gmail.com");
assertEquals(email, user.getEmail());
}
map() 对值应用(调用)作为参数的函数,然后将返回的值包装在 Optional 中。这就使对返回值进行链试调用的操作成为可能 —— 这里的下一环就是 orElse()。
相比这下,flatMap() 也需要函数作为参数,并对值调用这个函数,然后直接返回结果。
下面的操作中,我们给 User 类添加了一个方法,用来返回 Optional:
public class User {
private String position;
public Optional<String> getPosition() {
return Optional.ofNullable(position);
}
//...
}
既然 getter 方法返回 String 值的 Optional,你可以在对 User 的 Optional 对象调用 flatMap() 时,用它作为参数。其返回的值是解除包装的 String 值:
@Test
public void whenFlatMap_thenOk() {
User user = new User("anna@gmail.com", "1234");
user.setPosition("Developer");
String position = Optional.ofNullable(user)
.flatMap(u -> u.getPosition()).orElse("default");
assertEquals(position, user.getPosition().get());
}
2.6、过滤方法
除了转换值之外,Optional 类也提供了按条件“过滤”值的方法。
*filter()* 接受一个 *Predicate* 参数,返回测试结果为 true 的值。如果测试结果为 false,会返回一个空的 Optional。
来看一个根据基本的电子邮箱验证来决定接受或拒绝 User(用户) 的示例:
@Test
public void whenFilter_thenOk() {
User user = new User("anna@gmail.com", "1234");
Optional<User> result = Optional.ofNullable(user)
.filter(u -> u.getEmail() != null && u.getEmail().contains("@"));
assertTrue(result.isPresent());
}
如果通过过滤器测试,result 对象会包含非空值。
2.7、链式方法
为了更充分的使用 Optional,你可以链接组合其大部分方法,因为它们都返回相同类似的对象。
我们使用 Optional 重写最早介绍的示例。
首先,重构类,使其 getter 方法返回 Optional 引用:
public class User {
private Address address;
public Optional<Address> getAddress() {
return Optional.ofNullable(address);
}
// ...
}
public class Address {
private Country country;
public Optional<Country> getCountry() {
return Optional.ofNullable(country);
}
// ...
}
将之前的操作用Optional中的方法来进行操作:
@Test
public void whenChaining_thenOk() {
User user = new User("anna@gmail.com", "1234");
String result = Optional.ofNullable(user)
.flatMap(u -> u.getAddress())
.flatMap(a -> a.getCountry())
.map(c -> c.getIsocode())
.orElse("default");
assertEquals(result, "default");
}
然后将上面的代码进一步进行缩减:
String result = Optional.ofNullable(user)
.flatMap(User::getAddress)
.flatMap(Address::getCountry)
.map(Country::getIsocode)
.orElse("default");
参考博客:https://blog.csdn.net/wwe4023/article/details/80760416
3、总结
1、对于OrElse开头的方法来说,如果值为空,那么可以有两种使用方式:
- 赋予默认值;这里又分为两种情况,上面案例中可以看到orElse和orElseGet是有区别的;
- 抛出异常(不满足条件);
2、获取得到的Optional对象即使是一个EMPTY对象,也是可以调用当前的实例方法的。
Optional对象中包装的值才是我们需要保证操作的标准。
1、使用实例一
我们一般在使用Option的时候,通过在保证不为空的时候,可以使用of函数,但是不一定保证的话,那么需要使用ifPresent函数来进行选择性消费,因为这样子来进行操作安全性高一点。
我也自己写了一个例子来进行测试一下:
@Test
void contextLoads() {
User user = new User();
String s = Optional.ofNullable(user).map(u -> u.getAddress())
.map(address -> address.getCountry())
.map(country -> country.getIsSoCode())
.orElseGet(()->"code");
System.out.println(s);
}
全部为空就继续进行消费。
2、使用实例二
在使用的时候,为了防止值为空,需要赋予默认值:
public class DemoOne {
public static void main(String[] args) {
User user = new User();
// 假装这一步我们是不知道的
user = null;
System.out.println("main+++++"+Thread.currentThread().getName());
// 不只是能够对对象来进行操作!还可以操作对象中的属性!但是前提是要保证对象是空的
user = Optional.<User>ofNullable(user).orElseGet(()->{
System.out.println("lambda+++++"+Thread.currentThread().getName());
User user1 = new User();
System.out.println("ofNullable方法执行了........");
user1.setName("lig");
return user1;
});
user = null;
// 保证对象是空的,不如在使用的时候首先来进行判断
try {
Integer age = Optional.ofNullable(user)
// 这里如果不为空,那么直接获取得到的是里面的T值;如果为空,那么直接在线程栈中抛出异常
// 异常有两个作用:1、终止程序运行(不捕捉的情况下);2、抛出异常堆栈信息
.orElseThrow(() -> new NullPointerException("user对象为空"))
.getAge();
} catch (NullPointerException e) {
// 程序既然说是已经运行到了这里,那么就肯定已经符合预期的空指针异常了
// e.printStackTrace();
}
// 注意点:
// 1、那么这里还可以对这里的age来进行操作!那么是否可以考虑使用Optional<Optional>对象来进行操作
// 2、如果使用了Optional,可以考虑情况来使用get()方法等等,但是不推荐来进行使用!
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?