JDK1.8新特性之Optional
概念
Optional 是JDK1.8中出现的一个容器类,代表一个值存在或者不存在。原来使用null表示一个值不存在,现在Optional可以更好的表达这个概念。并且可以避免空指针异常。
场景分析
需求:假如我们要取一个男人心中的女神的名字。
假如不使用Optional来实现
男人Man.java
public class Man {
private Goddess goddess;
public Goddess getGoddess() {
return goddess;
}
public void setGoddess(Goddess goddess) {
this.goddess = goddess;
}
}
女神Goddess.java
public class Goddess {
private String name;
public Goddess(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试类Main.java
public class Main {
public static void main(String[] args) {
Man man = new Man();
System.out.println(getGoddessOfMan(man));
}
// 获取男人心中的女神
public static String getGoddessOfMan(Man man) {
return man.getGoddess().getName();
}
}
出现异常:
如果单看报错内容,我们可以知道是man.getGoddess().getName();
这条语句发生了空指针异常,但是我们还需要进一步定位才能知道究竟是man为null,还是goddess为null?
我们可以改写测试类代码来避免这个异常
// 获取男人心中的女神
public static String getGoddessOfMan(Man man) {
if (man == null || man.getGoddess() == null) {
return "";
}
return man.getGoddess().getName();
}
假如用上Optional来实现
改写Man.java
public class Man {
private Optional<Goddess> goddess = Optional.empty();
public Optional<Goddess> getGoddess() {
return goddess;
}
public void setGoddess(Optional<Goddess> goddess) {
this.goddess = goddess;
}
}
Goddess.java不改写
改写测试类Main.java
public class Main {
public static void main(String[] args) {
Man man = new Man();
System.out.println(getGoddessOfMan(Optional.ofNullable(man)));
man = null;
System.out.println(getGoddessOfMan(Optional.ofNullable(man)));
}
// 获取男人心中的女神
public static String getGoddessOfMan(Optional<Man> optionalMan) {
return optionalMan.orElse(new Man()).getGoddess().orElse(new Goddess("蒙娜丽莎")).getName();
}
}
控制台中打印出
蒙娜丽莎
蒙娜丽莎
其实,看上去也没有省几行代码嘛?那么我们接着往下分析Optional类。
分析Optional容器类
1.Optional的创建方法
Optional的核心且唯一的属性就是T value
。
另外,因为Optional的构造器都被私有化了,所以只能通过静态方法创建Optional对象。
1.1 静态创建方法of(T t) --- 不允许参数为null
测试代码如下,我们尝试传一个参数null
给of(T t)
方法,结果发生了NullPointerException
// of方法会判断参数是否为null,如果为null,会报空指针异常
Optional<Goddess> op = Optional.of(null);
我们深入java.util.Optional
的源代码
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
再追踪到java.util.Objects
的源代码
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
1.2 静态创建方法empty() --- 创建一个value=null
的Optional容器对象
再看java.util.Optional
中empty()
,返回一个成员变量value为null
的Optional容器对象
private static final Optional<?> EMPTY = new Optional<>();
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
1.3 静态创建方法ofNullable(T t) --- 允许参数为null
仍然看java.util.Optional
的源代码,ofNullable
表示可以接受null
,并使用empty()
返回。也接受参数value不为null
,使用of(T t)
返回。
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
2.Optional的判断和获取 --- 先使用isPresent()
判断,再使用get()
获取
测试代码如下
import java.util.Optional;
public class OptionalTest {
public static void main(String[] args) {
Optional<Goddess> op = Optional.of(new Goddess("蒙娜丽莎"));
if (op.isPresent()) {
System.out.println(op.get().getName());
}
Optional<Goddess> empty = Optional.ofNullable(null);
// 先通过isPresent()判断,再使用get()来避免直接使用empty.get().getName()可能带来NoSuchElementException异常
// if (empty.isPresent()) {
System.out.println(empty.get().getName());
// }
}
}
控制台输出如下
蒙娜丽莎
Exception in thread "main" java.util.NoSuchElementException: No value present
at java.util.Optional.get(Optional.java:135)
at optional.OptionalTest.main(OptionalTest.java:15)
从控制台输出我们可以知道,在使用get()
方法之前,最好先用isPresent()
判断Optional中的成员变量value
值是否存在。
3.把判断代码放在Optional类内的方法
3.2 orElse
方法 | 描述 |
---|---|
T orElse(T other) |
表示如果调用该方法的Optional对象的成员变量value 不为null 则返回value ,否则返回other |
T orElseGet(Supplier<T> supplier) |
表示如果调用该方法的Optional对象的成员变量value 不为null 则返回value ,否则用Supplier生成一个用于返回的T对象 |
T orElseThrow(Supplier<? extends X> exceptionSupplier) |
表示如果调用该方法的Optional对象的成员变量value 不为null 则返回value ,否则用Supplier生成一个用于抛出的异常对象 |
我们看可以查看一下java.util.Optional
源码
public T orElse(T other) {
return value != null ? value : other;
}
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
3.3 剩下的成员方法
方法 | 如果参数为null |
如果成员变量value 为null |
如果成员变量value 不为null |
---|---|---|---|
Optional<T> filter(Predicate<? super T> predicate) |
抛出NullPointerException |
返回empty() |
如果predicate.test(T t) 为true ,返回当前对象,否则返回empty() |
Optional<U> map(Function<? super T, ? extends U> mapper) |
抛出NullPointerException |
返回empty() |
转换T为U,再返回Optional.ofNullable(U) |
Optional<U> flatMap(Function<? super T, Optional<U>> mapper) |
抛出NullPointerException |
返回empty() |
转换T为Optional<U> ,转换后的对象如果为null ,抛出NullPointerException |
测试代码
import java.util.Optional;
public class OptionalTest {
public static void main(String[] args) {
Optional<Goddess> op = Optional.of(new Goddess("蒙娜丽莎"));
Optional<Goddess> nullOp = Optional.ofNullable(null);
// 如果女神名称不为null,filter返回op,否则返回empty()
System.out.println(op.filter(goddess->goddess.getName() != null).isPresent());
System.out.println(nullOp.filter(goddess->goddess.getName() != null).isPresent());
// 映射返回女神名称
System.out.println(op.map(Goddess::getName).get());
System.out.println(nullOp.map(Goddess::getName).isPresent());
// 映射返回装有女神名称的Optional容器对象
System.out.println(op.flatMap(goddess->Optional.ofNullable(goddess.getName())).get());
System.out.println(nullOp.flatMap(goddess->Optional.ofNullable(goddess.getName())).isPresent());
}
}
控制台输出如下:
true
false
蒙娜丽莎
false
蒙娜丽莎
false
再来看一下java.util.Optional
源码
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
public<U> Optional<U> flatMap(Function<? super T, Optional<U>> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Objects.requireNonNull(mapper.apply(value));
}
}
结语:回到最初的需求
假如我们扩展一下我们的需求
定义一个方法,获取男人心中的女神,要求男人不存在时抛出异常,如果男人存在,但是这个男人没有女神,那么给一个默认的女神名字-蒙娜丽莎
import java.util.Optional;
public class GetGoddessOfMan {
public static void main(String[] args) {
Man man = new Man();
System.out.println(getGoddessOfMan(Optional.ofNullable(man)));
man = null;
System.out.println(getGoddessOfMan(Optional.ofNullable(man)));
}
// 获取男人心中的女神
public static String getGoddessOfMan(Optional<Man> optionalMan) {
return optionalMan.orElseThrow(NullPointerException::new).getGoddess().orElse(new Goddess("蒙娜丽莎")).getName();
}
}
这里就比较简洁了,一句话就反映出了需求,且更接近自然语言。如果要用非Optional实现,代码类似下面这种
// 获取男人心中的女神
public static String getGoddessOfMan(Man man) {
Objects.requireNonNull(man);
Goddess goddess = man.getGoddess() == null ? new Goddess("蒙娜丽莎") : man.getGoddess();
return goddess.getName();
}
不过很多小伙伴还是会觉得这个写法看上去有些奇怪。在其他JDK1.8的新特性中,比如Stream流中也有返回Optional<T>
的函数,比如Optional<T> findFirst();
,Optional<T> findAny();
,Optional<T> max(Comparator<? super T> comparator)
, Optional<T> min(Comparator<? super T> comparator);
等等
JDK1.8新特性之Optional的更多相关文章
- JDK1.8新特性——Optional类
JDK1.8新特性——Optional类 摘要:本文主要学习了JDK1.8新增加的Optional类. 部分内容来自以下博客: https://www.cnblogs.com/1ning/p/9140 ...
- JDK1.8新特性——Collector接口和Collectors工具类
JDK1.8新特性——Collector接口和Collectors工具类 摘要:本文主要学习了在Java1.8中新增的Collector接口和Collectors工具类,以及使用它们在处理集合时的改进 ...
- JDK1.8新特性——Stream API
JDK1.8新特性——Stream API 摘要:本文主要学习了JDK1.8的新特性中有关Stream API的使用. 部分内容来自以下博客: https://blog.csdn.net/icarus ...
- JDK1.8新特性(一) ----Lambda表达式、Stream API、函数式接口、方法引用
jdk1.8新特性知识点: Lambda表达式 Stream API 函数式接口 方法引用和构造器调用 接口中的默认方法和静态方法 新时间日期API default Lambda表达式 L ...
- JDK1.8新特性之Stream类初识
JDK1.8新特性之Stream类初识 import java.util.Arrays; import java.util.List; import java.util.Optional; impor ...
- JDK1.8新特性之(三)--函数式接口
在上一篇文章中我们介绍了JDK1.8的新特性有以下几项. 1.Lambda表达式 2.方法引用 3.函数式接口 4.默认方法 5.Stream 6.Optional类 7.Nashorm javasc ...
- JDK1.8新特性之(一)--Lambda表达式
近期由于新冠疫情的原因,不能出去游玩,只能在家呆着.于是闲来无事,开始阅读JDK1.8的源代码.在开始之前也查询了以下JDK1.8的新特性,有针对性的开始了这段旅程. 只看不操作,也是不能心领神会的. ...
- JDK1.8新特性之(二)--方法引用
在上一篇文章中我们介绍了JDK1.8的新特性有以下几项. 1.Lambda表达式 2.方法引用 3.函数式接口 4.默认方法 5.Stream 6.Optional类 7.Nashorm javasc ...
- JDK1.7新特性
jdk1.7新特性 1 对集合类的语言支持: 2 自动资源管理: 3 改进的通用实例创建类型推断: 4 数字字面量下划线支持: 5 switch中使用string: 6 二进制字面量: 7 简化可变参 ...
随机推荐
- .net 后台页面统一验证是否登录
首先新写一个PageBase类 using System; using System.Collections.Generic; using System.Web; namespace Departme ...
- BZOJ 4029 [HEOI2015] 定价 ( 数位DP/贪心 )
前言 最近学了数位DP,感觉挺简单又实用.这道题就比较水,可以用300B的贪心过掉-网上似乎大多是贪心的题解,我就写写DP的做法 题意 给出正整数区间[L,R][L,R][L,R],定义荒谬值为 (去 ...
- 双击bin/startup.bat启动tomcat常见错误
双击bin/startup.bat启动tomcat常见错误: 常见错误:可能与其他服务的端口号冲突. tomcat的默认端口号8080,此端口号较为常见,建议修改此端口号. 修改方法: 点击conf文 ...
- 【题解】间隔排列-C++
题目Description小Q是班长.在校运动会上,小Q班要进行队列表演.小Q要选出2*N名同学编队,每人都被编上一个号,每一个从1到N的自然数都被某2名同学佩戴,现在要求将他们排成一列,使两个编号为 ...
- mysql远程服务密码修改
GRANT ALL PRIVILEGES ON *.* TO root@"%" IDENTIFIED BY "root"; FLUSH PRIVILEGE ...
- bufferedinputstream FileInputStream inputstream的比较
BufferedInputStream类相比InputStream类,提高了输入效率,增加了输入缓冲区的功能 不带缓冲的操作,每读一个字节就要写入一个字节,由于涉及磁盘的IO操作相比内存的操作要慢很多 ...
- 参数类型 (@Service层) impl
@Override public List<Map<String, Object>> selectAdvListByPosition(String adStructure, P ...
- Gym - 101955K Let the Flames Begin 约瑟夫环
Gym - 101955KLet the Flames Begin 说实话,没怎么搞懂,直接挂两博客. 小飞_Xiaofei的约瑟夫问题(Josephus Problem)3:谁最后一个出列 小飞_ ...
- LOJ166 拉格朗日插值 2【卷积,NTT】
题目链接:LOJ 题目描述:输入多项式的次数$n$,一个整数$m$和$f(0),f(1),f(2),\ldots,f(n)$,输出$f(m),f(m+1),f(m+2),\ldots,f(m+n)$ ...
- 简记乘法逆元(费马小定理+扩展Euclid)
乘法逆元 什么是乘法逆元? 若整数 \(b,m\) 互质,并且\(b|a\) ,则存在一个整数\(x\) ,使得 \(\frac{a}{b}\equiv ax\mod m\) . 称\(x\) 是\( ...