前言

只有光头才能变强

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

@FunctionalInterfacepublic interface Runnable {    public abstract void run();}

遍历HashMap的BiConsumer接口:

@FunctionalInterfacepublic 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接口:

@FunctionalInterfacepublic 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给大家用图的形式画一画:

给女朋友讲解什么是Optional【JDK 8特性】
Lambda表达式组成
以Runnable接口来举例:

给女朋友讲解什么是Optional【JDK 8特性】
Lambda表达式很简单!
再不济,我们在用IDE的时候,可以提示出Lambda表达式的语法的,这样可以帮我们快速上手Lambda表达式:

给女朋友讲解什么是Optional【JDK 8特性】
IDEA提示Lambda表达式
说白了,我们使用Lambda表达式的架子是这样的()->{},具体的时候看看函数式接口的抽象方法要求就可以了,再不济就使用IDE智能提示。

1.3泛型回顾

比如说public<U> Optional<U> map(Function<? super T, ? extends U> mapper)这个声明,你看懂了吗?

// 接口@FunctionalInterfacepublic 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,,可以是非null,提供了一系列的方法供我们判断该容器里的对象是否存在(以及后续的操作)。

Optional类的方法结构图:

给女朋友讲解什么是Optional【JDK 8特性】
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;@Datapublic 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);}

给女朋友讲解什么是Optional【JDK 8特性】
结果

2.2Optional容器简单的方法

// 得到容器中的对象,如果为null就抛出异常public T get() {    if (value == null) {        throw new NoSuchElementException("No value present");    }    return value;}// 判断容器中的对象是否为nullpublic 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的:

给女朋友讲解什么是Optional【JDK 8特性】
结果
我们调换一下顺序看看:

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());}

给女朋友讲解什么是Optional【JDK 8特性】
结果

2.3Optional容器进阶用法

当然了,我们到目前为止看起来Optional类好像就这么一回事了,这样代码写起来还不如我自己判断null呢…

我们对比一下:

给女朋友讲解什么是Optional【JDK 8特性】
对比
我们可以发现,手动判断是否为null好像还更方便简洁一点呢。

所以,我们带函数式接口的方法登场了!

2.3.1ifPresent方法

首先来看看ifPresent(Consumer<? super T> consumer)方法

public void ifPresent(Consumer<? super T> consumer) {    if (value != null)        consumer.accept(value);}@FunctionalInterfacepublic 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();}@FunctionalInterfacepublic 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();}// 接口@FunctionalInterfacepublic 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));    }}// 接口@FunctionalInterfacepublic 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函数返回值必须是Optionalpublic<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));}// 以前的代码v1public static String test2(User user) {    if (user != null) {        String name = user.getName();        if (name != null) {            return name.toUpperCase();        } else {            return null;        }    } else {        return null;    }}// 以前的代码v2public 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
    如果你觉得我写得还不错,了解一下:

  • 坚持原创的技术公众号:Java3y。回复 1 加入Java交流群
  • 文章的目录导航(精美脑图+海量视频资源):https://github.com/ZhongFuCheng3y/3y

给女朋友讲解什么是Optional【JDK 8特性】

坚持原创不易,给博主加鸡腿

更多相关文章

  1. 1.docker介绍安装,镜像管理,通过容器、模板创建镜像
  2. 2.docker容器管理、仓库管理、数据管理、数据卷备份恢复
  3. golang 发送post请求 其body中json对象使用变量作为value
  4. mall在Linux环境下的部署(基于Docker容器)
  5. Java 程序员应该知道的 10 个面向对象理论
  6. 一个GCRoot不可达的对象,会立刻被垃圾回收吗?
  7. 一文读懂 SuperEdge 边缘容器架构与原理
  8. Object对象你真理解了吗?

随机推荐

  1. Bottle 框架源码学习 四
  2. Javascript来自数据库的图像上的图像映射
  3. 使用js / php识别用户,无需登录[重复]
  4. python数据类型二(列表和元组)
  5. javascript写99乘法表
  6. 从打印页面中删除网址并打印文本
  7. 将敏感数据存储在数据库中,建议
  8. 查找setTimeout()中剩下的时间?
  9. 自定义ComboBox焦点不会切换到选择elem并
  10. 如何在v模型更改时触发事件? (vue js)