/ 今日科技快讯 /
近日,欧洲监管机构已要求谷歌提供更多信息,说明其现已取消的与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有如下好处:
代码判空处理确实更加优雅
学习其设计思想。其实与Kotlin的let,apply等在设计思想上是相通的。
当然,Optional并不是万能的,它也有相关缺点:
可读性变差。它带来的最直观的感受就是可读性变差了,原来直接判空的写法更直白,更利于理解。
维护风险。因为可读性变差,如果团队其他同事不了解相关语法(或者不熟练),在维护过程修改变更极有可能出错。
网上有文章说对性能会有提升,因为减少了临时变量。对此持怀疑态度,因为虽然临时变量有减少,但是增加了函数调用,函数调用也是要算性能开销的。
因为Optional操作中涉及到大量的函数接口和lambda表达式的使用,故我们先对这两者做一个基础介绍,若对此已经了解的话可以直接跳到Optional Api分类介绍。
/ 函数接口 /
在学习Optional之前,我们需要首先了解函数接口,因为Optional Api中很多参数都是函数接口对象。
Java 8 引入了2个比较重要的特性:
1. 函数接口
2. 支持在接口中通过default关键字提供默认实现。
注意:函数接口中仅有一个抽象方法。
我们首先看看都有哪些函数接口。这些函数接口被定义在 java.util.function包中,从参数数量维度可以分为3类:
无参数
1个参数
2个参数
无参数
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<T, R> {
R 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方法的前提是入参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表达式由三部分组成:
参数列表
箭头
主体
有两种风格,分别是:
表达式-风格
(parameters) -> expression
块-风格
(parameters) -> { statements; }
例:
() -> {}
() -> "hello"
() -> { return "hello"; }
其中()用来描述参数列表,{}用来描述方法体,-> 为lambda运算符,读作(goes to)。
lambda表达式字节码验证
拿Function接口为例,既然可以用lambda表示Function的实例,那lambda是如何实现apply方法的呢?编译器在编译过程中做了什么呢?
我们新增一个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文件,如下图所示:
查看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字节码文件中找到它。
查看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
4: return
LineNumberTable:
line 12: 0
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 14: 0
}
...
...
由此可见,我们在使用lambda表达式时,编译器会干下面几件事:
整个lambda表达式对应的匿名内部类仍然会被生成,并在字节码文件中体现出来,如上面的 Main$$Lambda$1.class
lambda表达式的代码块实现内容会以本地静态方法的形式记录起来,如上面的 lambdamainmainmain0(java.lang.Integer)
匿名内部类的抽象方法中会调用静态方法(即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 T 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和orElseGet都是典型的防御式编程。两者也仅仅只是体现在参数上的不同。
orElseThrow
定义
public T orElseThrow() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
public T 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种可能:
返回自身。跟当前引用的Optional是同一个对象。
返回empty Optional。不满足条件时则返回空Optional对象。
返回入参函数接口返回的Optional对象。
定义
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点:
入参函数返回类型必须跟当前引用的Optional是同一类型。如上处例子中是 Optional 类型。
入参函数返回对象一定不能为空,否则会抛空指针。
根据上面描述,组合函数有下面特点:
所有的组合Api传入的函数接口实例一定不能为null,否则第一时间就会报空指针。
除了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进行了分类归档,可以作为编码时的参考。
https://segmentfault.com/a/1190000023747150
欢迎关注我的公众号
学习技术或投稿
长按上图,识别图中二维码即可关注