专栏名称: 郭霖
Android技术分享平台,每天都有优质技术文章推送。你还可以向公众号投稿,将自己总结的技术心得分享给大家。
目录
相关文章推荐
郭霖  ·  一篇文章带你彻底掌握Optional ·  昨天  
郭霖  ·  音视频基础能力之 Android ... ·  2 天前  
郭霖  ·  Android - 监听网络状态 ·  6 天前  
51好读  ›  专栏  ›  郭霖

一篇文章带你彻底掌握Optional

郭霖  · 公众号  · android  · 2024-12-11 08:00

正文



/   今日科技快讯   /


近日,欧洲监管机构已要求谷歌提供更多信息,说明其现已取消的与Instagram母公司Meta Platforms的秘密广告合作关系,该合作关系无视该搜索公司关于未成年人线上待遇的规定。


/   作者简介   /


本篇文章转自程序员的自律生活的博客,文章主要分享了Optional相关的知识,相信会对大家有所帮助!


原文地址:

https://juejin.cn/post/7445957275204550666


/   前言   /


因为网上关于Optional的介绍大多仅仅只是停留在介绍Api上,缺少相关细节与总结,故在此重新整理一篇涉及Optional实现细节的详细文档,希望对大家有帮助。我们可以带着这些问题来阅读这篇文章:


  • Optional有哪些优劣势?

  • 什么是函数接口?

  • lambda表达式原理是怎样的?

  • lambda的方法引用是什么?

  • Optional提供的API可以分为哪几类?

  • map和flatMap 有什么区别?


/   Optional引用效果对比   /


举个简单的例子:获取汽车发动机名字。我们通过一个简单的demo来看看调用效果。


类定义


汽车类 :提供获取发动机对象函数


public class Car {
    private Engine engine;
    public Engine getEngine() {
        return engine;    }

发动机类 :提供获取发动机名函数


public class Engine {
    private final String name;
    public Engine(String name) {
        this.name = name;
    }
    public String getName() {
        return name;    }


获取发动机名


普通判空调用


private static String getEngineName(Car car) {
    String name = null;
    if (car != null) {
        Engine engine = car.getEngine();
        if (engine != null) {
            name = engine.getName();
        }
    }
    return name == null ? "UnKnown" : name;}


普通调用时,需要层层判空处理,car判空,engine判空,name也需要判空,层级越多,代码越复杂。


Optional调用


private static String getEngineNameWithOptionalMap(Optional car) {
    return car.map(Car::getEngine)
            .map(Engine::getName)
            .orElse("UnKnown");
}


可以看到optional调用中没有一个判空处理,整个代码行数也从10行减少到5行,减少50%。单从代码编写便利性的角度看,Optional优势还是比较显著的。


/   什么是Optional   /


Optional是Java 8引入的一个容器类,用于包含或不包含非空值。它的目的是提供一种更优雅的方式来处理可能为null的情况,从而避免 NullPointerException。


在Java 8之前,处理null值通常需要使用冗长的if语句来检查变量是否为null。Optional类提供了一种更简洁、更表达性的方式来处理这种场景。


经过本人实践下来,学习Optional有如下好处:


  1. 代码判空处理确实更加优雅

  2. 学习其设计思想。其实与Kotlin的let,apply等在设计思想上是相通的。


当然,Optional并不是万能的,它也有相关缺点:


  1. 可读性变差。它带来的最直观的感受就是可读性变差了,原来直接判空的写法更直白,更利于理解。

  2. 维护风险。因为可读性变差,如果团队其他同事不了解相关语法(或者不熟练),在维护过程修改变更极有可能出错。


网上有文章说对性能会有提升,因为减少了临时变量。对此持怀疑态度,因为虽然临时变量有减少,但是增加了函数调用,函数调用也是要算性能开销的。


因为Optional操作中涉及到大量的函数接口和lambda表达式的使用,故我们先对这两者做一个基础介绍,若对此已经了解的话可以直接跳到Optional Api分类介绍。


/   函数接口   /


在学习Optional之前,我们需要首先了解函数接口,因为Optional Api中很多参数都是函数接口对象。


Java 8 引入了2个比较重要的特性:


1. 函数接口
2. 支持在接口中通过default关键字提供默认实现。


注意:函数接口中仅有一个抽象方法。


我们首先看看都有哪些函数接口。这些函数接口被定义在 java.util.function包中,从参数数量维度可以分为3类:


  1. 无参数

  2. 1个参数

  3. 2个参数


无参数


  • Supplier : 无输入参数,但返回类型为R类型的函数方法。


1个参数


  • Function : 输入一个参数(类型为T),返回类型为R 类型的函数接口。

  • Consumer : 输入一个参数(类型为T),无返回值。

  • Predicate : 输入一个参数(类型为T),返回类型固定为boolean类型的函数接口。

  • To[基本类型]Function : 输入一个参数(类型为T),返回基本类型。比如ToIntFunction,ToLongFunction,ToDoubleFunction

  • UnaryOperator : 输入一个参数(类型为T),同时返回类型也为T。


2个参数


  • BiFunction : 输入2个参数(分别为T,U类型),返回R类型的函数接口。

  • BinaryOperator : 输入2个参数(都为T类型),同时返回类型也为T。

  • Comparator : 输入2个参数(都为T类型),同时返回类型固定为int值。


Function


@FunctionalInterface
public interface Function<TR{

    apply(T t);

    default  Function compose(Function super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default  Function andThen(Function super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static  Function identity() {
        return t -> t;
    }}


这里我们重点介绍下定义和默认实现,其他函数接口实现都类似,不同的只是接口方法名而已。


@FunctionalInterface


可以看到 Function 接口是被 @FunctionalInterface 注解修饰的(所有的函数接口基本上都有被该注解修饰),这表示该接口只允许定义一个抽象方法(即该函数接口需要被实现的方法),若定义多个抽象方法,在编译时会报错。


R apply(T t);


Function 接口实例需要实现的抽象方法,其签名为(T)-〉R,接收一个T类型的参数,返回R类型的对象。


compose


compose 方法是将另一个Function实例的执行与当前Function实例的执行组合起来,先执行入参Function,再执行自身Function。


  • compose 方法是被default 修饰的,说明compose方法是Function 接口的默认实现,Function 实例在实现Function 接口时可以直接使用默认实现,也可以覆盖重写compose 方法。

  • compose表示2个函数接口实例按先后顺序执行:先执行入参Function实例的apply方法,然后执行自身Function实例的apply方法。


注意:使用compose方法的前提是入参Function的apply方法刚好返回当前Function apply方法的入参类型T,这样才能将2者连接起来。


andThen


andThen也是结合两个Function并执行对应的apply方法,与compose的差异在于执行先后顺序,andThen 是先执行自身Function的apply方法,再执行入参Function的apply 方法。


identity()


identity返回一个Function实例,返回值即入参值。


注意:compose、anThen、identity 方法均是返回一个新的Function 实例,这就意味着调用这些方法时,入参Function 和 自身Function 的apply方法不会立即执行,只是指定了执行顺序规则,需要等返回的新Function执行apply时才会具体执行。基于此,我们可以用该语法设置回调等处理。比如等到网络请求后再更新UI。更新网络请求的结果刚好是更新UI的入参,此时就可以用andThen。


关于函数接口名大家不用死记硬背,在使用时多翻两遍文档就好,多使用几次就记住了。


/   lambda表达式   /


语法


Lambda表达式由三部分组成:


  1. 参数列表

  2. 箭头

  3. 主体



有两种风格,分别是:


表达式-风格


(parameters) -> expression


块-风格


(parameters) -> { statements; }


例:


  1. () -> {}

  2. () -> "hello"

  3. () -> { return "hello"; }


其中()用来描述参数列表,{}用来描述方法体,-> 为lambda运算符,读作(goes to)。


lambda表达式字节码验证


拿Function接口为例,既然可以用lambda表示Function的实例,那lambda是如何实现apply方法的呢?编译器在编译过程中做了什么呢?


  1. 我们新增一个Main.java 文件,并在main方法中定义一个 Function 实例add对象,然后执行add 函数实例方法,并打印结果。


public class Main {
    public static void main(String[] args) {
        Function add = (t)->2*t;
        System.out.println("add result:" + add.apply(2));
    }}


2. 执行程序


java -Djdk.internal.lambda.dumpProxyClasses

com.yyt.myapplication.Main


执行上面指令我们得到下图结果,这是符合预期的。



注意,在运行时加上vm参数-Djdk.internal.lambda.dumpProxyClasses就可以得到lambda实例对应独立的内部类class文件,如下图所示:



  1. 查看Main$$Lambda$1.class 内部类的内容


直接通过AndroidStuido打开class文件,内容如下:


//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.yyt.myapplication;

import java.lang.invoke.LambdaForm.Hidden;
import java.util.function.Function;

// $FF: synthetic class
final class Main$$Lambda$1 implements Function {
    private Main$$Lambda$1() {
    }

    @Hidden
    public Object apply(Object var1) {
        return Main.lambda$main$0((Integer)var1);
    }}


可以发现,该类确实是Function接口的一个具体实现类,尤其是实现了apply方法,在apply方法中会执行Main.lambdamainmainmain0((Integer)var1)方法,我们可以在Main.class字节码文件中找到它。


  1. 查看Main.class 文件(仅列出核心部分)

...
...
...
#14 = Class              #47            // com/yyt/myapplication/Main

#51 = Methodref          #14.#73        // com/yyt/myapplication/Main.lambda$main$0:(Ljava/lang/Integer;)Ljava/lang/Integer;

#73 = NameAndType        #22:#23        // lambda$main$0:(Ljava/lang/Integer;)Ljava/lang/Integer;
...
...
...
{
  public com.yyt.myapplication.Main();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."":()V
         4return
      LineNumberTable:
        line 120


  private static java.lang.Integer lambda$main$0(java.lang.Integer);
    descriptor: (Ljava/lang/Integer;)Ljava/lang/Integer;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=2, locals=1, args_size=1
         0: iconst_2
         1: aload_0
         2: invokevirtual #13                 // Method java/lang/Integer.intValue:()I
         5: imul
         6: invokestatic  #8                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         9: areturn
      LineNumberTable:
        line 140
}
...
...


  • 通过#51的描述可以看出来 Main.lambdamainmainmain0代表Main类中的方法lambdamainmainmain0

  • 第25行定义了lambdamainmainmain0方法,可以看到它是一个private的静态方法,通过descriptor描述可以看出它就是我们的lambda表达式代码块的实现部分。


由此可见,我们在使用lambda表达式时,编译器会干下面几件事:


  1. 整个lambda表达式对应的匿名内部类仍然会被生成,并在字节码文件中体现出来,如上面的 Main$$Lambda$1.class

  2. lambda表达式的代码块实现内容会以本地静态方法的形式记录起来,如上面的 lambdamainmainmain0(java.lang.Integer)

  3. 匿名内部类的抽象方法中会调用静态方法(即lambda代码块内容)


由此可见,虽然我们没有显示的new Function的实例,但是通过lambda表达式也可以隐式的生成其匿名实例。


/   Optimal内部Api介绍   /


Optional实现的原理其实很简单,就是在内部维护一个泛型实例T value对象,想要访问value都得通过Optioanl来实现,这就达到封装的效果(判空处理就是封装在了Optional内部)。


我们可以将Optional 的Api分为下面几类:


  • 创建

  • 查询

  • 判断

  • 执行

  • 组合


创建


Optional 提供了3个静态方法来创建Optional对象实例。


empty()


定义


private final T value;
private static final Optional> EMPTY = new Optional<>(null);

public static Optional empty() {
    @SuppressWarnings("unchecked")
    Optional t = (Optional) EMPTY;
    return t;}


使用


Optional car = Optional.empty();


说明


通过empty快速创建Optional对象时,其内部维护的value对象就为null(没有被赋值)。empty返回的是一个全局唯一的实例,因为Optional没有提供setValue 相关方法,所以不用担心出问题。


of

定义


public static  Optional of(T value) {
    return new Optional<>(Objects.requireNonNull(value));
}
    
//注意,requireNonNull 是Objects类中的方法,为了简便起见和 Optional#of方法展示在一起了。
public static  requireNonNull(T obj) {
    if (obj == null)
        throw new NullPointerException();
    return obj;}


使用


Optional car = Optional.of(new Car());


说明


使用of创建Optional对象,则一定要确保传入的参数对象不为空,不然of方法内部通过 Objects.requireNonNull(value)抛出空指针。


ofNullable


定义


public static  Optional ofNullable(T value) {
    return value == null ? (Optional) EMPTY
                         : new Optional<>(value);
}


使用


    Optional car = Optional.ofNullable(null); 
    或者 
    Optional car = Optional.ofNullable(new Car()); 


说明


使用ofNullable创建Optional对象时,则不用关心传入的参数是不是null,为空则返回empty Optional对象,不为空,则构建一个新的Optional对象。


查询


get


定义


 public T get() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}


使用


    Optional car = Optional.of(null); 
    Car value = car.get(); //此时会抛 NoSuchElementException 异常
    
    Optional car = Optional.of(new Car());
    Car value = car.get(); //正常获取新构建的Car实例。


说明


可以通过get方法获取构建Optional对象时传入的具体实例,若在构建Optional 时没有传入值,则get方法抛 NoSuchElementException 异常。一般不会直接通过get方法获取实例,不然就失去了Optional的意义了。


orElse


定义


public T orElse(T other) {
    return value != null ? value : other;
}


使用


    Optional car1 = Optional.empty(); 
    Car car2 = new Car();
    Car finalCar = car1.orElse(car2); // finalCar == car2


说明


当不知道Optional中的value是否为空时,可以采用防御性编程,若为空,则给其指定一个具体的实例。其实该方法效果跟 if-else效果是一样的。


orElseGet


定义


 
public T orElseGet(Supplier extends T> supplier) {
    return value != null ? value : supplier.get();
}


使用


    Optional car1 = Optional.empty(); 
    Car finalCar = car1.orElse(Car car2 = ()->new Car()); // finalCar == car2


说明


orElseGet和orElse效果是一样的,不同的是:


  • orElse:通过参数直接指定具体的value实例。在执行orElse之前就已经确定。

  • orElseGet:通过传入函数接口实例,在执行orElseGet时通过函数返回值才确定具体值。


orElse和orElseGet都是典型的防御式编程。两者也仅仅只是体现在参数上的不同。


orElseThrow


定义


public T orElseThrow() {
    if (value == null) {
        throw new NoSuchElementException("No value present");
    }
    return value;
}

public  orElseThrow(Supplier extends X> exceptionSupplier) throws X {
    if (value != null) {
        return value;
    } else {
        throw exceptionSupplier.get();
    }
}


使用


    Optional car = Optional.empty(); 
    Car car1 = car.orElseThrow(); // 抛出 "No value present" 异常
    
    Car car1 = car.orElseThrow(()->{new Exception("test error")}); // 抛出 "test error" 异常    


说明


orElseThrow内部有重载2个方法,一个无参,另一个参数支持指定函数接口 来返回自定义的异常。若value不为空,则直接返回。


stream


定义


  public Stream stream() {
    if (!isPresent()) {
        return Stream.empty();
    } else {
        return Stream.of(value);
    }
}


使用


     Optional car = Optional.empty(); 
    Stream stream= car.stream(); // 返回Stream 实例


说明


可以通过stream 连续执行相关读写操作。


判断


isPresent() 和 isEmpty()


定义


public boolean isPresent() {
    return value != null;
}

public boolean isEmpty() {
    return value == null;
}


使用


    Optional car = Optional.empty(); 
    boolean isPresent = car.isPresent(); // 返回false
    boolean isEmpty = car.isEmpty(); // 返回true


说明


isPresent和isEmpty都是判断value的值,只是判断的角度不一样,isPresent 时判断value是否有值,而isEmpty则是判断value是否为空。


执行


ifPresent


定义


  public void ifPresent(Consumer super T> action) {
    if (value != null) {
        action.accept(value);
    }
}


使用


     Optional car = Optional.of(new Car()); 
    //输出打印"print it if value not null"
    car.ifPresent((t)->{System.out.println("print it if value not null"});


说明


ifPresent表示如果value不为空,则执行入参函数接口实例对应的代码块,注意函数接口的入参也必须和Optional的value类型一致,即上面的参数t其实也是Car类型,从源码中可以看出,传入的就是value值, 我们可以在lambda代码块中继续引用它。


ifPresentOrElse


定义


  public void ifPresentOrElse(Consumer super T> action, Runnable emptyAction) {
    if (value != null) {
        action.accept(value);
    } else {
        emptyAction.run();
    }
}


使用


      Optional car = Optional.of(new Car()); 
    //输出打印"value not null"
    car.ifPresentOrElse((t)->{System.out.println("value not null"
                        ,()->{System.out.println("value is null"} );
或者
    Optional car = Optional.empty(); 
    //输出打印"value is null"
    car.ifPresentOrElse((t)->{System.out.println("value not null"
                        ,()->{System.out.println("value is null"} );


说明


ifPresentOrElse表示如果value不为空,则执行入参函数接口实例对应的代码块;若value为空,则执行指定的Runnable接口实例。ifPresentOrElse是ifPresent的升级款,考虑value存在和不存在2种情况,并且均给出处理方案。效果就是ifPresent + orElse组合的效果。


ifPresentOrElse


定义


  public void ifPresentOrElse(Consumer super T> action, Runnable emptyAction) {
    if (value != null) {
        action.accept(value);
    } else {
        emptyAction.run();
    }
}


使用


     Optional car = Optional.of(new Car()); 
    //输出打印"value not null"
    car.ifPresentOrElse((t)->{System.out.println("value not null"
                        ,()->{System.out.println("value is null"} );
或者
    Optional car = Optional.empty(); 
    //输出打印"value is null"
    car.ifPresentOrElse((t)->{System.out.println("value not null"
                        ,()->{System.out.println("value is null"} );


说明


ifPresentOrElse表示如果value不为空,则执行入参函数接口实例对应的代码块;若value为空,则执行指定的Runnable接口实例。ifPresentOrElse是ifPresent的升级款,考虑value存在和不存在2种情况,并且均给出处理方案。效果就是ifPresent + orElse组合的效果。


组合


所有的组合Api均返回一个Optional对象, 返回的Optional对象有3种可能:


  1. 返回自身。跟当前引用的Optional是同一个对象。

  2. 返回empty Optional。不满足条件时则返回空Optional对象。

  3. 返回入参函数接口返回的Optional对象。

filter


定义


 public Optional filter(Predicate super T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent()) {
        return this;
    } else {
        return predicate.test(value) ? this : empty();
    }}


使用


    Optional car = Optional.of(new Car()); 
    car.filter((t)->{t != null}); //返回 car 对象(因为满足条件,则返回自身)
    car.filter((t)->{t == null}); //返回 empty Optional 对象(因为不满足条件,则返回empty对象)


说明


filter用来对当前Optional对象做过滤处理,只要记住这一点即可,只要一个条件不满足就会返回empty Optional对象。


map


定义


 public  Optional map(Function super T, ? extends U> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return empty();
    } else {
        return Optional.ofNullable(mapper.apply(value));
    }
}


使用


    Optional car = Optional.of(new Car()); 
    car.map((t)->t.getEngine()); //返回 Optional(Engine) 实例
    car.map(Car::getEngine); //返回 Optional(Engine) 实例


说明


map用来映射一个新的Optional对象。如上面例子,通过car对象映射到 Engine对象了。例子中传入的参数(t)->t.getEngine()和Car::getEngine的效果是一样的,我们在最后一小节来说明。


flatMap


定义


public  Optional flatMap(Function super T, ? extends Optional extends U>> mapper) {
    Objects.requireNonNull(mapper);
    if (!isPresent()) {
        return empty();
    } else {
        @SuppressWarnings("unchecked")
        Optional r = (Optional) mapper.apply(value);
        return Objects.requireNonNull(r);
    }}


使用


    Optional car = Optional.of(new Car()); 
    car.flatMap((t)->new Optional(t.getEngine())); //返回 Optional(Engine) 实例


说明


flatMap和map的效果是一模一样的,它们均是在Optional value不为空时才能生效。


不同点有下面3个方面:



基于以上不同点,当你无法确定函数中返回的对象是否为空时,最好使用map,不然flatMap内部可能就会抛出空指针了。


or


定义


public Optional or(Supplier extends Optional extends T>> supplier) {
    Objects.requireNonNull(supplier);
    if (isPresent()) {
        return this;
    } else {
        @SuppressWarnings("unchecked")
        Optional r = (Optional) supplier.get();
        return Objects.requireNonNull(r);
    }}


使用


    Optional car = Optional.empty(); 
    car.or(()->new Optional(new Car())); //返回 Optional 实例


说明


or还是防御性编程,当前Optional为empty时,会尝试通过函数接口返回的相同类型的Optional对象。注意2点:


  1. 入参函数返回类型必须跟当前引用的Optional是同一类型。如上处例子中是 Optional 类型。

  2. 入参函数返回对象一定不能为空,否则会抛空指针。


根据上面描述,组合函数有下面特点:


  1. 所有的组合Api传入的函数接口实例一定不能为null,否则第一时间就会报空指针。

  2. 除了map和filter返回的Optional对象可能为empty,其他的返回对象一定不能为空,否则跑空指针。


/   再谈lambda表达式   /


      Optional car = Optional.of(new Car()); 
    car.map((t)->t.getEngine()); //返回 Optional(Engine) 实例
    car.map(Car::getEngine); //返回 Optional(Engine) 实例


为什么 (t)->t.getEngine() 和 Car::getEngine 效果是一样的?


当在Lambda表达式中仅调用引用实例的一个方法时可以使用方法引用,其写法为目标引用::方法名称。


指向静态方法的方法引用


Function fun = s -> Integer.parseInt(s);
Function fun = Integer::parseInt;


指向任意类型实例方法的方法引用


Function fun = s -> s.length();
Function fun = String::length;


指向现存外部对象实例方法的方法引用


String s = "hello";
Supplier len = () -> s.length();
Supplier len = s::length;


从上可以看出,我们上面案例中是使用的第2种引用方式。第2种和第3种方式的区别在于定义的函数签名方式,若是无参数则用第3种方式,若是需要有参数,则使用第2种(此处参数就是实例自身)。


/   总结   /


我们首先介绍了Optional编写代码带来的简洁性,然后介绍了Java 8开始引入的函数接口和lambda表达式,并且对lambda的生效过程进行了字节码验证;最后详细介绍了Optional的Api,对所有Api进行了分类归档,可以作为编码时的参考。


lambda表达式介绍

https://segmentfault.com/a/1190000023747150


推荐阅读:
我的新书,《第一行代码 第3版》已出版!
多台Android设备局域网下的数据备份如何实现?
AOSP打包应用多语言随记


欢迎关注我的公众号

学习技术或投稿



长按上图,识别图中二维码即可关注