前言
只有光头才能变强
前两天带女朋友去图书馆了, 随手就给她来了一本《与孩子一起学编程》的书, 于是今天就给女朋友讲解一下什么是 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 一个出来啊.
当然了, 我的女朋友是现实存在的.
来源: https://www.cnblogs.com/Java3y/p/9985618.html