前言

只有光头才能变强

前两天带女朋友去图书馆了,随手就给她来了一本《与孩子一起学编程》的书,于是今天就给女朋友讲解一下什么是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一个出来啊。

当然了,我的女朋友是现实存在的。

参考资料:

如果你觉得我写得还不错,了解一下:

给女朋友讲解什么是Optional【JDK 8特性】的更多相关文章

  1. JDK新特性关于流操作部分

    // array 工具类 可以用来快捷的将数组转化为list List<String> strings = Arrays.asList("zhongguo", &quo ...

  2. 给女朋友讲解什么是Git

    前言 在周六发现了Linus去Google演讲的一个视频,当时还发了一条朋友圈: 有兴趣的同学也可以去看看,一点儿也不无聊,在线看Linus大佬怼人 https://www.bilibili.com/ ...

  3. JavaEE基础(二十七)/反射、JDK新特性

    1.反射(类的加载概述和加载时机) A:类的加载概述 当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现对这个类进行初始化. 加载  就是指将class文件读入 ...

  4. day27(027-反射&JDK新特性)

    ###27.01_反射(类的加载概述和加载时机) A:类的加载概述 *加载   就是指将class文件读入内存,并为之创建一个Class对象.任何类被使用时系统都会建立一个Class对象. *连接  ...

  5. 史上最全jdk新特性总结,涵盖jdk8到jdk15!

    前言 在本文中,我将描述自第8版以来Java最重要且对开发人员友好的功能.为什么会有这样的主意?在Web上,您可以找到许多文章,其中包含每种Java版本的新功能列表.但是,由于缺少文章,因此无法简要概 ...

  6. JDK 新特性

    Jdk8新特性 一:接口默认方法和静态方法: 我们可以在接口中定义默认方法,使用default关键字,并提供默认的实现.所有实现这个接口的类都会接受默认方法的实现,除非子类提供的自己的实现. 我们还可 ...

  7. jdk 11特性

    JDK 11 总共包含 17 个新的 JEP ,分别为: 181: Nest-Based Access Control(基于嵌套的访问控制) 309: Dynamic Class-File Const ...

  8. jdk 8 特性

    date相关: 1.在jdk 8之前,由于Date,Calendar的烂设计(烂的原因:日期计算复杂,Date没有时区),催生了一个优秀的第三方时间框架:Joda-Time(解决了:日期的计算,时区) ...

  9. 第十四章 JDK新特性回顾

    14.1.JDK5新特性回顾 自动装箱.拆箱 静态导入 增强for循环 可变参数 枚举 泛型 元数据 14.2.JDK7新特性回顾 对Java集合(Collections)的增强支持 在switch中 ...

随机推荐

  1. window.open打开新窗体并用post方式传参

    function openPostWindow(url,data,name){ //url要跳转到的页面,data要传递的数据,name显示方式(可能任意命名) var tempForm = docu ...

  2. QUIC协议的分析,性能测试以及在QQ会员实践

    WeTest 导读 你听过HTTPS.HTTP2.0.SPDY,但是这些应用层协议都是基于可靠的传输层协议TCP来实现的.那么,基于高效的UDP协议有没有一种相对可靠的应用层协议呢? Why QUIC ...

  3. cocos2d-x_ Windows下Android环境搭建

    在Windows环境下编译cocos2d-x-3.0 Android-NDK编译:cocos2d-x(二) Mac 下搭建:http://www.cocoachina.com/bbs/read.php ...

  4. Generator的正确打开方式

    前两年大量的在写Generator+co,用它来写一些类似同步的代码但实际上,Generator并不是被造出来干这个使的,不然也就不会有后来的async.await了Generator是一个可以被暂停 ...

  5. PAT1040:Longest Symmetric String

    1040. Longest Symmetric String (25) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, ...

  6. mysql分表经验总结

    一.为什么要分表? 当一张的数据达到几百万时,你查询一次所花的时间会变多,如果有联合查询的话,有可能会死在那儿了.分表的目的就在于此,减小数据库的负担,缩短查询时间.根据个人经验,mysql执行一个s ...

  7. 使用Docker镜像和仓库

    https://www.rhel.cc/2014/11/50/   学习目标: 什么是镜像 docker镜像是由文件系统折叠加而成的,最低端是一个引导文件系统,即bootfs: 如何对镜像进行管理 使 ...

  8. windows10系统终极净化方法

    去年购入一台华硕FL8000U,性能很是不错,但是硬件只能兼容win10,不支持win7(linux倒是可以,但是始终用不顺手),win10里面杂七杂八的确实很多,本人重度强迫症+洁癖+极简主义,所以 ...

  9. 几种扫描二维码工具的User-Agent

    微信: user-agent: Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_3 like Mac OS X) AppleWebKit/603.3.8 (KHTML, ...

  10. Spring Cloud构建微服务架构(二)服务消费者

    Netflix Ribbon is an Inter Process Communication (IPC) cloud library. Ribbon primarily provides clie ...