欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-05-25 17:09:21
前言 只有光头才能变强 前两天带女朋友去图书馆了,随手就给她来了一本《与孩子一起学编程》的书,于是今天就给女朋友讲解一下什么是Optional类。 至于她能不能看懂,那肯定是看不懂的。(学到变量/for循环的女人怎么能看懂呢) 不知道大家还记得上一篇 "《阿里巴巴 Java开发手册》读后感" 不,当 ......

前言

只有光头才能变强

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

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

以runnable接口来举例:

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

再不济,我们在用ide的时候,可以提示出lambda表达式的语法的,这样可以帮我们快速上手lambda表达式:

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

说白了,我们使用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类的方法结构图:

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

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

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

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的:

给女朋友讲解什么是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);
}

@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类深度解析:
  • java8 如何正确使用 optional:
  • 【java】jdk8 optional 的正确姿势

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