给女朋友讲解什么是Optional【JDK 8特性】
前言
只有光头才能变强
前两天带女朋友去图书馆了,随手就给她来了一本《与孩子一起学编程》的书,于是今天就给女朋友讲解一下什么是Optional类。
- 至于她能不能看懂,那肯定是看不懂的。(学到变量/for循环的女人怎么能看懂呢)
不知道大家还记得上一篇《阿里巴巴 Java开发手册》读后感不,当时阅读到空指针异常(NPE)时,书上提到JDK 8有个Optional类供我们使用,该类可以尽可能地防止出现空指针异常(NPE)。
文本力求简单讲清每个知识点,希望大家看完能有所收获
一、基础铺垫
我们都知道JDK 8最重要的新特性是Lambda表达式,这个可以让我们简化非常多的代码编写,不知道大家会使用了没有。这里我简单跟大家来回顾一下~
1.1Lambda简化代码例子
下面就以几个例子来看看Lambda表达式是怎么简化我们代码的编写的。
首先我们来看看创建线程:
public static void main(String[] args) {
// 用匿名内部类的方式来创建线程
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("公众号:Java3y---回复1进群交流");
}
});
// 使用Lambda来创建线程
new Thread(() -> System.out.println("公众号:Java3y---回复1进群交流"));
}
再来看看遍历Map集合:
public static void main(String[] args) {
Map<String, String> hashMap = new HashMap<>();
hashMap.put("公众号", "Java3y");
hashMap.put("交流群", "回复1");
// 使用增强for的方式来遍历hashMap
for (Map.Entry<String, String> entry : hashMap.entrySet()) {
System.out.println(entry.getKey()+":"+entry.getValue());
}
// 使用Lambda表达式的方式来遍历hashMap
hashMap.forEach((s, s2) -> System.out.println(s + ":" + s2));
}
在List中删除某个元素
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Java3y");
list.add("3y");
list.add("光头");
list.add("帅哥");
// 传统的方式删除"光头"的元素
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
if ("光头".equals(iterator.next())) {
iterator.remove();
}
}
// Lambda方式删除"光头"的元素
list.removeIf(s -> "光头".equals(s));
// 使用Lambda遍历List集合
list.forEach(s -> System.out.println(s));
}
从上面的例子我们可以看出,Lambda表达式的确是可以帮我们简化代码的。
1.1函数式接口
使用Lambda表达式,其实都是建立在函数式接口上的。我们看看上面的代码的接口:
创建多线程的Runnable接口:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
遍历HashMap的BiConsumer接口:
@FunctionalInterface
public interface BiConsumer<T, U> {
void accept(T t, U u);
default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
Objects.requireNonNull(after);
return (l, r) -> {
accept(l, r);
after.accept(l, r);
};
}
}
在List中删除元素的Predicate接口:
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
函数式接口的特点:由@FunctionalInterface
注解标识,接口有且仅有一个抽象方法!
1.2Lambda简单讲解
或许我们一开始看到Lambda的时候,发现Lambda表达式的语法有点奇葩,甚至有点看不懂。没事,这里3y给大家用图的形式画一画:
以Runnable接口来举例:
再不济,我们在用IDE的时候,可以提示出Lambda表达式的语法的,这样可以帮我们快速上手Lambda表达式:
说白了,我们使用Lambda表达式的架子是这样的()->{}
,具体的时候看看函数式接口的抽象方法要求就可以了,再不济就使用IDE智能提示。
1.3泛型回顾
比如说public<U> Optional<U> map(Function<? super T, ? extends U> mapper)
这个声明,你看懂了吗?
// 接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
在泛型的上限和下限中有一个原则:PECS(Producer Extends Consumer Super)
- 带有子类限定的可以从泛型读取【也就是--->(? extend T)】-------->Producer Extends
- 带有超类限定的可以从泛型写入【也就是--->(? super T)】-------->Consumer Super
解析:传入的参数是泛型 T 或者其父类,返回值是U或其子类。
具体可参考:
二、Optional类
一句话介绍Optional类:使用JDK8的Optional类来防止NPE(空指针异常)问题。
接下来我们看看文档是怎么说的:
A container object which may or may not contain a non-null value.Additional methods that depend on the presence or absence of a contained value are provided
它是一个容器,装载着非NULL元素(或者没有装载元素),提供了一系列的方法供我们判断该容器里的对象是否存在(以及后续的操作)。
Optional类的方法结构图:
2.1创建Optional容器
我们先来看看Optional的属性以及创建Optional容器的方法:
// 1、创建出一个Optional容器,容器里边并没有装载着对象
private static final Optional<?> EMPTY = new Optional<>();
// 2、代表着容器中的对象
private final T value;
// 3、私有构造方法
private Optional() {
this.value = null;
}
// 4、得到一个Optional容器,Optional没有装载着对象
public static<T> Optional<T> empty() {
@SuppressWarnings("unchecked")
Optional<T> t = (Optional<T>) EMPTY;
return t;
}
// 5、私有构造方法(带参数),参数就是具体的要装载的对象,如果传进来的对象为null,抛出异常
private Optional(T value) {
this.value = Objects.requireNonNull(value);
}
// 5.1、如果传进来的对象为null,抛出异常
public static <T> T requireNonNull(T obj) {
if (obj == null)
throw new NullPointerException();
return obj;
}
// 6、创建出Optional容器,并将对象(value)装载到Optional容器中。
// 传入的value如果为null,抛出异常(调用的是Optional(T value)方法)
public static <T> Optional<T> of(T value) {
return new Optional<>(value);
}
// 创建出Optional容器,并将对象(value)装载到Optional容器中。
// 传入的value可以为null,如果为null,返回一个没有装载对象的Optional对象
public static <T> Optional<T> ofNullable(T value) {
return value == null ? empty() : of(value);
}
所以可以得出创建Optional容器有两种方式:
- 调用ofNullable()方法,传入的对象可以为null
- 调用of()方法,传入的对象不可以为null,否则抛出NullPointerException
下面我们简单就可以看看用法了:
现在我有一个User对象,这里用到了Lombok,有兴趣的同学可去学学了解一下:两个月的Java实习结束,继续努力
import lombok.Data;
@Data
public class User {
private Integer id;
private String name;
private Short age;
}
测试:
public static void main(String[] args) {
User user = new User();
User user1 = null;
// 传递进去的对象不可以为null,如果为null则抛出异常
Optional<User> op1 = Optional.of(user1);
// 传递进去的对象可以为null,如果为null则返回一个没有装载对象的Optional容器
Optional<User> op2 = Optional.ofNullable(user);
}
2.2Optional容器简单的方法
// 得到容器中的对象,如果为null就抛出异常
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
// 判断容器中的对象是否为null
public boolean isPresent() {
return value != null;
}
// 如果容器中的对象存在,则返回。否则返回传递进来的参数
public T orElse(T other) {
return value != null ? value : other;
}
这三个方法是Optional类比较常用的方法,并且是最简单的。(因为参数不是函数式接口)
下面我们继续看看用法:
public static void main(String[] args) {
User user = new User();
User user1 = null;
Optional<User> op1 = Optional.ofNullable(user);
System.out.println(op1.isPresent());
System.out.println(op1.get());
System.out.println(op1.orElse(user1));
}
结果很明显,因为我们的user是不为null的:
我们调换一下顺序看看:
public static void main(String[] args) {
User user = new User();
User user1 = null;
Optional<User> op1 = Optional.ofNullable(user1);
System.out.println(op1.isPresent());
System.out.println(op1.orElse(user));
System.out.println(op1.get());
}
2.3Optional容器进阶用法
当然了,我们到目前为止看起来Optional类好像就这么一回事了,这样代码写起来还不如我自己判断null呢...
我们对比一下:
我们可以发现,手动判断是否为null好像还更方便简洁一点呢。
所以,我们带函数式接口的方法登场了!
2.3.1ifPresent方法
首先来看看ifPresent(Consumer<? super T> consumer)
方法
public void ifPresent(Consumer<? super T> consumer) {
if (value != null)
consumer.accept(value);
}
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
如果容器中的对象存在,则调用accept方法,比如说:
public static void main(String[] args) {
User user = new User();
user.setName("Java3y");
test(user);
}
public static void test(User user) {
Optional<User> optional = Optional.ofNullable(user);
// 如果存在user,则打印user的name
optional.ifPresent((value) -> System.out.println(value.getName()));
// 旧写法
if (user != null) {
System.out.println(user.getName());
}
}
2.3.2orElseGet和orElseThrow方法
直接看源码:
// 如果对象存在,则直接返回,否则返回由Supplier接口的实现用来生成默认值
public T orElseGet(Supplier<? extends T> other) {
return value != null ? value : other.get();
}
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// 如果存在,则返回。否则抛出supplier接口创建的异常
public <X extends Throwable> T orElseThrow(Supplier<? extends X> exceptionSupplier) throws X {
if (value != null) {
return value;
} else {
throw exceptionSupplier.get();
}
}
例子:
public static void main(String[] args) {
User user = new User();
user.setName("Java3y");
test(user);
}
public static void test(User user) {
Optional<User> optional = Optional.ofNullable(user);
// 如果存在user,则直接返回,否则创建出一个新的User对象
User user1 = optional.orElseGet(() -> new User());
// 旧写法
if (user != null) {
user = new User();
}
}
总的来说跟我们上面所讲的orElse()
差不多,只不过它可以通过Supplier接口的实现来生成默认值。
2.3.3filter方法
直接看源码:
// 如果容器中的对象存在,并且符合过滤条件,返回装载对象的Optional容器,否则返回一个空的Optional容器
public Optional<T> filter(Predicate<? super T> predicate) {
Objects.requireNonNull(predicate);
if (!isPresent())
return this;
else
return predicate.test(value) ? this : empty();
}
// 接口
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
返回Optional对象我们就可以实现链式调用了!
例子:
public static void test(User user) {
Optional<User> optional = Optional.ofNullable(user);
// 如果容器中的对象存在,并且符合过滤条件,返回装载对象的Optional容器,否则返回一个空的Optional容器
optional.filter((value) -> "Java3y".equals(value.getName()));
}
2.3.4map方法
直接看源码:
// 如果容器的对象存在,则对其执行调用mapping函数得到返回值。然后创建包含mapping返回值的Optional,否则返回空Optional。
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));
}
}
// 接口
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
}
例子:
public static void test(User user) {
Optional<User> optional = Optional.ofNullable(user);
// 如果容器的对象存在,则对其执行调用mapping函数得到返回值。然后创建包含mapping返回值的Optional,否则返回空Optional。
optional.map(user1 -> user1.getName()).orElse("Unknown");
}
// 上面一句代码对应着最开始的老写法:
public String tradition(User user) {
if (user != null) {
return user.getName();
}else{
return "Unknown";
}
}
2.3.5flatMap方法
直接看源码:
// flatMap方法与map方法类似,区别在于apply函数的返回值不同。map方法的apply函数返回值是? extends U,而flatMap方法的apply函数返回值必须是Optional
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));
}
}
2.3.6总结
再来感受一下Optional的魅力
public static void main(String[] args) {
User user = new User();
user.setName("Java3y");
System.out.println(test(user));
}
// 以前的代码v1
public static String test2(User user) {
if (user != null) {
String name = user.getName();
if (name != null) {
return name.toUpperCase();
} else {
return null;
}
} else {
return null;
}
}
// 以前的代码v2
public static String test3(User user) {
if (user != null && user.getName() != null) {
return user.getName().toUpperCase();
} else {
return null;
}
}
// 现在的代码
public static String test(User user) {
return Optional.ofNullable(user)
.map(user1 -> user1.getName())
.map(s -> s.toUpperCase()).orElse(null);
}
Optional总结:
filter,map或flatMap一个函数,函数的参数拿到的值一定不是null。所以我们通过filter,map 和 flatMap之类的函数可以将其安全的进行变换,最后通过orElse系列,get,isPresent 和 ifPresent将其中的值提取出来。
其实吧,用Optional类也没有简化很多的代码,只是把NPE异常通过各种方法隐藏起来(包装了一层)。通过Lambda表达式可以让我们处理起来更加"优雅"一些。
三、最后
之前在初学的时候没在意JDK8的特性,其实JDK更新很多时候都能给我们带来不少好处的(简化代码编写,提高性能等等),所以作为一名Java程序员,还是得多学学新特性。(话说JDK9该类又有新特性了...)
如果你要评论“醒醒吧,程序员哪来的女朋友”,“我尿黄,让我来”之类的话,我建议你是不是好好反省一下自己,为什么别的程序员都有女朋友,就你没有,是不是自己技术不过关了?通过“工厂”找一个有那么难吗?再不济也能自己new一个出来啊。
当然了,我的女朋友是现实存在的。
参考资料:
- Java 8 Optional类深度解析:https://www.cnblogs.com/xingzc/p/5778090.html
- Java8 如何正确使用 Optional:http://www.importnew.com/26066.html
- https://www.zhihu.com/question/63783295/answer/214531004
- 【Java】jdk8 Optional 的正确姿势https://blog.csdn.net/hj7jay/article/details/52459334
如果你觉得我写得还不错,了解一下:
- 文章的目录导航(精美脑图+海量视频资源):https://github.com/ZhongFuCheng3y/3y

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
-
上一篇
ossutil发布1.4.2版本,支持上传或复制文件目录指定存储类型,支持访问请求者付费模式的Bucket
ossutil 1.4.2 Release Note lscp命令:支持访问开启“请求者付费模式”的Bucket cp命令:上传、复制文件或目录时,支持设置存储类型 cp命令:上传下载时,显示传输速度 cp命令:Window版本支持断点续传(Linux、Mac版本已经支持) ossutil 最新版本下载和使用说明,请参考ossutil帮助文档 使用ossutil访问开启“请求者付费模式”的Bucket 说明: 1.请求者付费模式:当Bucket开启请求者付费模式后,则由请求者为其访问产生的请求费用付费,而数据拥有者为存储空间付费。可参考请求者付费模式介绍2.授权说明:需要该Bucket拥有者,将Bucket授权给您的访问控制子账号。比如,通过Bucket Policy将权限授权给您的RAM 子账号 ID,可参考 Bucket Policy使用说明
-
下一篇
OSS brower js SDK
浅谈 今天带来的是 OSS brower js SDK 的安装过程和使用的 demo 测试用例。 环境准备 OSS brower js SDK 是基于 node js 框架上的服务端程序,服务端启动以后,提供客户端的访问地址。 准备 node js 安装,最好在 9.x.x 以上版本,我当前的测试版本是 v10.9.0 测试浏览器环境 ( IE>=10,主流版本的 Chrome/Firefox/Safari,主流版本的 Android/iOS/WindowsPhone ) 开始安装 下载源码 git 库 git clone https://github.com/ali-sdk/ali-oss.git npm 开始安装 cd ali-oss ,执行 npm install cd example ,执行 npm install tips :因为部分浏览器不支持 promise,需要引入 promise 兼容库。 例如:IE10 和 IE11 需要引入 promise-polyfill 。 修改配置文件 1、OSS brower 自带集成了 STS 生成的功能,其实就是在本地启动了一个...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- Docker容器配置,解决镜像无法拉取问题
- Windows10,CentOS7,CentOS8安装Nodejs环境
- MySQL8.0.19开启GTID主从同步CentOS8
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Linux系统CentOS6、CentOS7手动修改IP地址
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2整合Redis,开启缓存,提高访问速度
- 设置Eclipse缩进为4个空格,增强代码规范