封面来源:本文封面来源于网络,如有侵权,请联系删除。
参考 黑马程序员Java函数式编程全套视频教程,Lambda表达式、Stream流、函数式编程一套全通关 ,用于查漏补缺。
本站更多推荐阅读:
美团技术团队对函数式编程的讲解:
1. 闭包
什么是闭包?
Lambda 表达式使用外部定义的变量(局部变量、成员变量、静态变量),函数对象和它外界的变量绑定在一起,就形成了闭包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @ FunctionalInterface
private interface Op {
Integer op ( Integer param );
}
int NUMBER_INT = 2 ;
static int STATIC_INT = 3 ;
@ Test
public void testClosure () {
int a = 1 ;
// Lambda 表达式绑定了局部变量 a
Op op = b -> a + b ;
assertThat ( op . op ( 1 ) ) . isEqualTo ( 2 );
// Lambda 表达式绑定了成员变量
op = b -> NUMBER_INT + b ;
assertThat ( op . op ( 1 ) ) . isEqualTo ( 3 );
// Lambda 表达式绑定了静态变量
op = b -> STATIC_INT + b ;
assertThat ( op . op ( 1 ) ) . isEqualTo ( 4 );
}
使用的局部变量(成员变量、静态变量没这个限制)必须是 final 或者 effective final,为什么呢?
一句话:函数不可变。这个函数可能被传递到任何地方。
并且函数与外部变量绑定后,如果外部变量实例发生变化,那原先的实例岂不是可能会被 GC?如果要保证不被 GC,编译器不得做更多的工作来处理这些数据?
final 或者 effective final 保证对象实例不变,但不能保证对象的状态不变。
函数式编程要求函数产生的结果只与传入函数的参数有关,如果对象状态发生了改变,这其实并不符合函数式编程的要求,但在实际开发场景中是可以这么做的,不一定非得完全满足函数式编程的要求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @ AllArgsConstructor
private static class Obj {
private String text ;
}
/**
* FP:Functional Programming
*/
@ Test
public void testBreakFP () {
Obj obj = new Obj ( "hello" ) ;
Function < String , String > func = i -> obj . text + " " + i ;
assertThat ( func . apply ( "world" ) ) . isEqualTo ( "hello world" );
// 要求 obj 实例不变,但可以修改内部字段
obj . text = "fxxk" ;
assertThat ( func . apply ( "world" ) ) . isEqualTo ( "fxxk world" );
}
使用闭包能够为函数提供除参数以外的其他数据。
2. 柯里化
什么是柯里化?
让接收多个参数的函数转换成一系列接收一个参数的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @ FunctionalInterface
interface Fa {
Fb op ( List < Integer > list );
}
@ FunctionalInterface
interface Fb {
Fc op ( List < Integer > list );
}
@ FunctionalInterface
interface Fc {
List < Integer > op ( List < Integer > list );
}
Fb step1 () {
List < Integer > a = Lists . newArrayList ( 1 , 2 , 3 );
Fa fa = i -> j -> k -> {
List < Integer > list = new ArrayList <> (i) ;
list . addAll (j);
list . addAll (k);
return list ;
} ;
return fa . op (a);
}
Fc step2 ( Fb fb) {
List < Integer > b = Lists . newArrayList ( 4 , 5 , 6 );
return fb . op (b);
}
List < Integer > step3 ( Fc fc) {
List < Integer > c = Lists . newArrayList ( 7 , 8 , 9 );
return fc . op (c);
}
@ Test
public void testCurrying () {
List < Integer > list = step3 ( step2 ( step1 ())) ;
assertThat (list) . containsExactly ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 );
}
上述应用和 Step Builder 的实现类似,或者说 Step Builder 就是柯里化的应用。
柯里化的实现需要结合闭包,比如:fn = a -> b -> a + b,最后的 b -> a + b 中的 a 是一个闭包变量。
使用柯里化实现让函数分布执行。
我自己在工作中使用柯里化是为了“偷懒”。比如一个接收 3 个参数且没有返回值的函数式接口(类似于 Consumer),JDK 并未内置这样的函数式接口,此时我就会使用柯里化来实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 /**
* <p>
* 命名参考:
* <ul>
* <li>Unary: 一元</li>
* <li>Binary: 二元</li>
* <li>Ternary: 三元</li>
* <li>Quaternary: 四元</li>
* </ul>
* </p>
*/
@ FunctionalInterface
interface TernaryConsumer < T , U , R > {
void accept ( T t , U u , R r );
}
@ Test
public void testTernaryConsumer () {
TernaryConsumer < Double , Float , Integer > consumer = (t , u , r) -> {
System . out . println (t + ";" + u + ";" + r);
} ;
// 1.0;2.0;3
consumer . accept ( 1.0 , 2f , 3 );
Function < Double , Function < Float , Consumer < Integer >>> f = t -> u -> r -> {
System . out . println (t + ";" + u + ";" + r);
} ;
// 1.0;2.0;3
f . apply ( 1.0 ). apply ( 2f ). accept ( 3 );
Function < Double , BiConsumer < Float , Integer >> fun = t -> (u , r) -> {
System . out . println (t + ";" + u + ";" + r);
} ;
// 1.0;2.0;3
fun . apply ( 1.0 ). accept ( 2f , 3 );
}
3. 高阶函数
所谓高阶,就是指它是其他函数对象的使用者。
高阶函数是指那些可以接受函数作为参数,或者返回函数作为结果的函数。
使用高阶函数,能够:
将通用、复杂的逻辑隐含在高阶函数内
将易变、未定的逻辑放在外部的函数对象中
3.1 应用:内循环
无需关心循环怎么实现,只关心集合中每个元素的处理逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 private < T > void higherOrder ( List < T > list , Consumer < T > consumer) {
// 指定迭代器中第一个元素的索引
ListIterator < T > iterator = list . listIterator ( list . size ());
// 倒着遍历
while ( iterator . hasPrevious () ) {
T t = iterator . previous ();
consumer . accept (t);
}
}
@ Test
public void testInnerLoop () {
List < Integer > list = Lists . newArrayList ( 1 , 2 , 3 , 4 , 5 );
List < Integer > newList = new ArrayList <> () ;
higherOrder (list , newList :: add) ;
assertThat (newList) . containsExactly ( 5 , 4 , 3 , 2 , 1 );
}
3.2 应用:二叉树
梳理遍历二叉树的方式,包括递归与非递归。
二叉树节点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class TreeNode {
@ Getter
int val ;
TreeNode left ;
TreeNode right ;
public TreeNode ( int val ) {
this . val = val;
}
public TreeNode ( int val , TreeNode left , TreeNode right ) {
this . val = val;
this . left = left;
this . right = right;
}
}
非递归版本的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 // 非递归版本的实现
public static void traverse ( TreeNode root , TraversalType type , Consumer < TreeNode > consumer) {
// 用来记住回去的路
Deque < TreeNode > stack = new ArrayDeque <> () ;
// 当前节点
TreeNode curr = root ;
// 上次处理的节点
TreeNode last = null ;
while (curr != null || ! stack . isEmpty () ) {
// 一路向左
if (curr != null ) {
stack . push (curr);
// 前序:向左前就获取节点
if (type == TraversalType . PRE ) {
consumer . accept (curr);
}
curr = curr . left ;
} else {
TreeNode peek = stack . peek ();
// 没有右子树
if ( peek . right == null ) {
// 中序、后序
if (type == TraversalType . IN || type == TraversalType . POST ) {
consumer . accept (peek);
}
last = stack . pop ();
} else if ( peek . right == last){ // 有右子树,但是走完了
// 后序
if (type == TraversalType . POST ) {
consumer . accept (peek);
}
last = stack . pop ();
} else { // 有右子树,且没有走过
// 中序
if (type == TraversalType . IN ) {
consumer . accept (peek);
}
// 定位到右子树,然后再向左
curr = peek . right ;
}
}
}
}
递归版本的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 递归版本的实现
public static void recursiveTraversal ( TreeNode root , TraversalType type , Consumer < TreeNode > consumer) {
if (root == null ) return ;
if (type == TraversalType . PRE ) {
consumer . accept (root);
}
recursiveTraversal ( root . left , type , consumer) ;
if (type == TraversalType . IN ) {
consumer . accept (root);
}
recursiveTraversal ( root . right , type , consumer) ;
if (type == TraversalType . POST ) {
consumer . accept (root);
}
}
4. Stream API
4.1 流的转换
Stream#mapMulti() 方法是在 JDK16 中引入的,用于将流中的元素映射到零个、一个或多个。
1 < R > Stream < R > mapMulti ( BiConsumer < ? super T , ? super Consumer < R >> mapper)
mapper 参数的类型是 BiConsumer,这个函数式接口接收两个参数:
流中的元素
一个 Consumer,它消费的数据会添加到新的流中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 private void deepFlatten ( Object e , Consumer < Object > c) {
if (e instanceof Iterable < ? > elements) {
elements . forEach (ie -> deepFlatten (ie, c));
} else if (e != null ) {
c . accept (e);
}
}
@ Test
public void testMapMulti () {
List < String > list = List . of ( "Tom" , "Jerry" );
// 将一个转换成多个
List < String > result = list . stream (). < String > mapMulti ((s , c) -> {
// s 的全大写、全小写会添加到新的流中
c . accept ( s . toUpperCase ());
c . accept ( s . toLowerCase ());
}) . toList ();
assertThat (result) . hasSize ( 4 )
. containsExactlyInAnyOrder ( "TOM" , "tom" , "JERRY" , "jerry" );
// 深度扁平化
var nestedList = List . of ( 1 , 2 , List . of ( 3 , 4 , List . of ( 5 , 6 )), List . of ( 7 , 8 ));
List < Integer > integerList = nestedList . stream ()
. mapMulti ( this :: deepFlatten)
. map (String :: valueOf)
. map (Integer :: valueOf)
. toList ();
assertThat (integerList) . hasSize ( 8 )
. hasSizeBetween ( 1 , 8 );
}
4.2 流的截取
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // takeWhile:条件成立保留,一旦条件不成立,剩下的都不要
@ Test
public void testTakeWhile () {
List < Integer > list = Stream . of ( 1 , 2 , 3 , 7 , 8 , 9 , 4 , 5 , 6 )
. takeWhile (i -> i <= 6 )
. toList ();
// 只要不大于 6 的,碰到大于 6 的剩下的都不要
assertThat (list) . containsExactly ( 1 , 2 , 3 );
}
// dropWhile:条件成立舍弃,一旦条件不成立,剩下的都保留
@ Test
public void testDropWhile () {
List < Integer > list = Stream . of ( 1 , 2 , 3 , 7 , 8 , 9 , 4 , 5 , 6 )
. dropWhile (i -> i <= 6 )
. toList ();
// 不大于 6 的都不要,一碰到大于 6 的剩下都要
assertThat (list) . containsExactly ( 7 , 8 , 9 , 4 , 5 , 6 );
}
4.3 流的生成
1 2 // 生成 5 个 0 - 100 之间的随机数
ThreadLocalRandom . current (). ints ( 5 , 0 , 100 );
4.4 查找与判断
1 2 3 4 5 6 7 8 9 10 11 // anyMatch、allMatch 作用在空列表上时的区别
@ Test
public void testMatch () {
List < String > list = new ArrayList <> () ;
assertThat ( list . stream (). allMatch ( "test" :: equals) ) . isTrue ();
assertThat ( list . stream (). allMatch (i -> ! "test" . equals (i)) ) . isTrue ();
assertThat ( list . stream (). noneMatch ( "test" :: equals) ) . isTrue ();
assertThat ( list . stream (). noneMatch (i -> ! "test" . equals (i)) ) . isTrue ();
assertThat ( list . stream (). anyMatch (i -> ! "test" . equals (i)) ) . isFalse ();
assertThat ( list . stream (). anyMatch ( "test" :: equals) ) . isFalse ();
}
对于空集合来说:
allMatch() 与 noneMatch() 总是返回 true;
anyMatch() 总是返回 false。
5. 并行流
Collector.of() 方法的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @ Test
public void testCreateCollector () {
ArrayList < Object > arrayList = Stream . of ( 1 , 2 , 3 , 4 )
. parallel ()
. collect ( Collector . of (
ArrayList ::new , // 如何创建容器
ArrayList :: add, // 如何向容器添加数据
(list1, list2) -> {
list1 . addAll (list2);
return list1;
}, // 如何合并两个容器
list -> list // 收尾
// 还有个特性参数:是否支持并发、是否需要收尾、是否要保证收集的顺序 -> 当前情况:不支持并发、需要收尾、要保证收集的顺序
));
assertThat (arrayList) . containsExactly ( 1 , 2 , 3 , 4 );
}
并行流执行逻辑:
数据量问题:数据量大时才建议使用 并行流
并行流使用的线程会无限增加吗?与 CPU 能处理的线程数有关
收尾的意义:转不可变集合、StringBuilder 转 String 等将合并的数据再进行转换
是否线程不安全:安全
Collector 对 of() 方法进行了重载,增加了 Characteristics 类型的参数,它表示一些特性:
是否需要进行收尾,默认收尾
是否需要保证顺序,默认保证顺序
容器是否需要支持并发,默认不支持
1 2 3 4 5 6 7 public static < T , A , R > Collector < T , A , R > of ( Supplier < A > supplier ,
BiConsumer < A , T > accumulator ,
BinaryOperator < A > combiner ,
Function < A , R > finisher ,
Characteristics ... characteristics ) {
// --snip--
}
Characteristics 是一个枚举,它有三个可选值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 enum Characteristics {
/**
* 容器需要支持并发
*/
CONCURRENT ,
/**
* 容器中的数据不保证顺序
*/
UNORDERED ,
/**
* 不需要收尾
*/
IDENTITY_FINISH
}
CONCURRENT 常常与 UNORDERED 一起使用,如果设置了这两个值,收集器创建的容器应当是线程安全的容器。在执行过程中,收集器只创建一次容器,并且不再执行容器合并的操作,因为此时创建的容器是线程安全的,多个线程可以同时操作这个容器。
如果没有设置这两个值,无需保证收集器创建的容器是线程安全的,但为保证线程安全,执行的每个线程都会创建一个新容器,每个线程只会操作自己新创建的容器,而不会操作其他线程的容器,从而保证最终的线程安全。
也就是说,现在有 2 种方案:
Characteristics.CONCURRENT + Characteristics.UNORDERED + 线程安全的容器
默认 + 线程不安全的容器
2 种方案均有优劣,应根据实际情况选择:
创建容器的次数减少,占用内存较少,但在数据量巨大的情况下,线程竞争会降低程序的性能
创建容器的次数更多,占用内存更大
6. Lambda 表达式的序列化
参考 Lambda 与序列化 一文。
7. Lambda 表达式的原理
7.1 基本原理
Lambda 表达式是一种语法糖,它仍会被翻译为类、对象和方法。
方法从哪来?
编译器发现代码中出现了 Lambda 表达式,就会在当前类中生成 private static 方法,方法内包含对应 Lambda 表达式的逻辑。这可以通过使用反射 API 的 getDeclaredMethods() 获取生成的方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Lambda01Test implements WithAssertions {
@ Test
public void test () {
BinaryOperator < Integer > lambda = (a, b) -> a + b;
Method [] methods = Lambda01Test . class . getDeclaredMethods ();
// 方法不止一个
assertThat (methods). hasSizeGreaterThan ( 1 );
for ( Method method : methods) {
String name = method . getName ();
// 其中有一个方法的方法名中包含 lambda$test$
if ( ! name . contains ( "lambda$test$" ) && ! name . contains ( "test" )) {
Assertions . fail ();
}
}
}
}
类和对象从哪来?
运行期间动态生成。
可以按需指定以下虚拟机参数,让生成的 class 文件保存到磁盘上:
1 2 3 4 5 # JDK 11 中指定的参数
-Djdk.internal.lambda.dumpProxyClasses
# JDK 21 中指定的参数
-Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles
以上述 test() 方法为例,在 JDK21 的环境下,添加 VM options 为 -Djdk.invoke.LambdaMetafactory.dumpProxyClassFiles 后运行,生成 DUMP_LAMBDA_PROXY_CLASS_FILES 目录,目录中存在如下生成的类:
1 2 3 4 5 6 7 8 9 // $FF: synthetic class
final class Lambda01Test$$Lambda implements BinaryOperator {
private Lambda01Test$$Lambda () {
}
public Object apply ( Object var1 , Object var2 ) {
return Lambda01Test . lambda$test$0 ((Integer)var1, (Integer)var2);
}
}
在生成的类中,调用了在原始类中生成的静态方法。
那 Lambda 表达式对应的类和对象是怎么在运行期间生成的呢?
这涉及到 LambdaMetafactory.metafactory() 方法的使用。
使用 LambdaMetafactory.metafactory() 过程中又涉及到 MethodHandle 的使用,可以参考 方法句柄 一文,这里不再赘述。
还是以下列 Lambda 表达式为例:
1 BinaryOperator < Integer > lambda = (a , b) -> a + b ;
假设在编译期间已经生成了以下方法:
1 2 3 private static Integer lambda$test$ ( Integer a , Integer b) {
return a + b ;
}
而对于运行期间生成的类,可以假设它与下列的 MyLambda 类似:
1 2 3 4 5 6 7 8 9 static final class MyLambda implements BinaryOperator < Integer > {
private MyLambda () {
}
@ Override
public Integer apply ( Integer a , Integer b ) {
return lambda$test$ (a, b);
}
}
那现在的问题就是:怎么在运行期间生成了 MyLambda 对象?
这就需要用到 LambdaMetafactory.metafactory() 方法。
这个方法的参数特别多,有多达 6 个,看一眼它的签名:
1 2 3 4 5 6 7 8 9 public static CallSite metafactory ( MethodHandles . Lookup caller ,
String interfaceMethodName ,
MethodType factoryType ,
MethodType interfaceMethodType ,
MethodHandle implementation ,
MethodType dynamicMethodType)
throws LambdaConversionException {
// --snip--
}
先看下 6 个参数的含义:
caller:接收一个 Lookup 对象,它要有访问生成的类的权限;
interfaceMethodName:接口方法名,也就是可以写成 Lambda 表达式的函数式接口中的方法名;
factoryType:工厂类型,创建生成的类对象的工厂方法对应的 MethodType。以假定的生成类 MyLambda 为例,如果需要使用一个工厂方法来构建 MyLambda 对象,这个工厂方法不需要任何参数,返回值类型是 BinaryOperator;
interfaceMethodType:接口方法类型,指函数式接口中的方法对应的 MethodType。以使用的 BinaryOperator 为例,其内部的方法都使用了泛型,因此这些参数和返回值类型应该是 Object;
implementation:生成的方法的 MethodHandle,这里就是 lambda$test$() 对应的 MethodHandle;
dynamicMethodType:动态方法类型,这里指的是生成类 MyLambda 中 apply() 方法对应的 MethodType。
返回的 CallSite 对象又有什么用呢?
可以调用它的 getTarget() 方法获取到一个 MethodHandle 对象,表示用于创建函数对象的工厂方法的 MethodHandle,也就是创建 MyLambda 对象的工厂方法对应的 MethodHandle。
那后续不就可以调用 MethodHandle 的 invoke() 方法获取到函数对象?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 @ Test
@ SneakyThrows
@ SuppressWarnings ( "unchecked" )
public void testLambdaMetaFactory () {
MethodHandles . Lookup lookup = MethodHandles . lookup ();
MethodHandle impl = lookup . findStatic (
LambdaPrincipleTest . class ,
"lambda$test$" ,
MethodType . methodType ( Integer . class , Integer . class , Integer . class )
);
CallSite callSite = LambdaMetafactory . metafactory (
// 1. lookup
lookup,
// 2. 接口方法名
"apply" ,
// 3. 创建函数对象工厂方法的长相:方法的返回值、参数
MethodType . methodType ( BinaryOperator . class ),
// 4. 接口方法的长相,对于 BinaryOperator 来说,其方法使用了泛型,因此都是 Object
MethodType . methodType ( Object . class , Object . class , Object . class ),
// 5. 实现方法的 MethodHandle
impl,
// 6. 函数对象实际长相
MethodType . methodType ( Integer . class , Integer . class , Integer . class )
);
// 函数对象的工厂方法的 MethodHandle
MethodHandle factory = callSite . getTarget ();
BinaryOperator < Integer > invoke = ( BinaryOperator < Integer > ) factory . invoke ();
// 上述大段代码等价于: BinaryOperator<Integer> lambda = (a, b) -> a + b;
assertThat ( invoke . apply ( 1 , 2 ) ) . isEqualTo ( 3 );
}
7.3 执行时机
目前已知可以使用 LambdaMetafactory.metafactory() 方法在运行时生成函数对象,那它是什么时候执行的呢?
通过反编译查看生成的字节码文件可知,执行到 Lambda 表达式时会执行 invokedynamic 虚拟机指令,之后就会去执行 LambdaMetafactory.metafactory() 方法生成函数对象。
8. 方法引用的原理
方法引用的原理基本与 Lambda 表达式的原理一致,它也会被翻译成类和对象,但是不会在编译期间生成静态方法了,它会直接使用已经存在的方法。
比如存在 Student 类:
1 2 3 4 5 6 7 static class Student {
private String name ;
public String getName () {
return name;
}
}
可以编写这样一个方法引用:
1 Function < Student , String > func = Student :: getName ;
在运行时,会生成类和对象,生成的类中不再调用编译期间生成的静态方法(编译期间也不会生成静态方法),而是直接调用 Student 中的 getName() 方法。
生成的类和下列代码类似:
1 2 3 4 5 6 7 8 9 static final class MyMethodReference implements Function < Student , String > {
private MyMethodReference () {
}
@ Override
public String apply ( Student student ) {
return student . getName ();
}
}
想要验证的话,也可以像验证 Lambda 表达式生成的类那样,添加虚拟机参数后运行程序。
运行时生成函数对象也会使用 LambdaMetafactory.metafactory() 方法,就像:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @ Test
@ SneakyThrows
@ SuppressWarnings ( "unchecked" )
public void testMethodReferenceMetaFactory () {
MethodHandles . Lookup lookup = MethodHandles . lookup ();
// 需要成员方法 getName 的 MethodHandle
MethodHandle impl = lookup . findVirtual (
Student . class ,
"getName" ,
MethodType . methodType ( String . class )
);
CallSite cs = LambdaMetafactory . metafactory (
lookup,
"apply" ,
MethodType . methodType ( Function . class ),
MethodType . methodType ( Object . class , Object . class ),
impl,
MethodType . methodType ( String . class , Student . class )
);
Function < Student , String > invoke = ( Function < Student , String > ) cs . getTarget (). invoke ();
// Function<Student, String> func = Student::getName;
Student student = new Student () ;
student . name = "mofan" ;
assertThat ( invoke . apply (student) ) . isEqualTo ( "mofan" );
}
9. 闭包的原理
闭包只是 Lambda 表达式中引用了外部的变量,因此闭包的原理与前文中 Lambda 表达式的原理基本一致,同样需要生成类、对象和方法。
引用外部变量可能有 3 中情况:
局部变量
静态变量
成员变量
引用局部变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 @ Test
public void test1 () {
int c = 10 ;
BinaryOperator < Integer > lambda = (a , b) -> a + b + c ;
assertThat ( lambda . apply ( 1 , 2 ) ) . isEqualTo ( 13 );
}
private static Integer lambda$test$ ( int c , Integer a , Integer b) {
return a + b + c ;
}
static final class MyClosure1 implements BinaryOperator < Integer > {
private final int arg$1 ;
private MyClosure1 ( int arg $ 1 ) {
this . arg$1 = arg$1;
}
@ Override
public Integer apply ( Integer a , Integer b ) {
return lambda$test$ (arg$1, a, b);
}
}
生成的方法不再是两个参数,而是三个参数,第一个参数是引用的外部局部变量类型。
生成的类的构造函数不再是无参的,需要一个与引用的外部局部变量类型相同的参数。这确实很好理解,当 Lambda 表达式传递给其他方法时,其他方法访问不到原先作用域中的局部变量,因此在构建函数对象时需要保存引用的局部变量。
引用静态变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static int d = 100 ;
@ Test
public void test2 () {
BinaryOperator < Integer > lambda = (a , b) -> a + b + d ;
assertThat ( lambda . apply ( 1 , 2 ) ) . isEqualTo ( 103 );
// 对于静态变量,并不要求其是 final 的
d = 10 ;
assertThat ( lambda . apply ( 1 , 2 ) ) . isEqualTo ( 13 );
}
private static Integer lambda$test$$ ( Integer a , Integer b) {
return a + b + ClosurePrincipleTest . d ;
}
static final class MyClosure2 implements BinaryOperator < Integer > {
public MyClosure2 () {
}
@ Override
public Integer apply ( Integer a , Integer b ) {
return lambda$test$$ (a, b);
}
}
生成的方法又回到两个参数,这是因为在方法内部可以通过 类名.静态变量 的形式访问到目标静态变量,同样,生成的类的构造函数也回归无参。
引用成员变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int e = 1000 ;
@ Test
public void test3 () {
BinaryOperator < Integer > lambda = (a , b) -> a + b + e ;
assertThat ( lambda . apply ( 1 , 2 ) ) . isEqualTo ( 1003 );
// 对于静态变量,也不要求其是 final 的
e = 10 ;
lambda = (a , b) -> a + b + e ;
assertThat ( lambda . apply ( 1 , 2 ) ) . isEqualTo ( 13 );
}
private Integer lambda$test$$$ ( Integer a , Integer b) {
return a + b + this . e ;
}
static final class MyClosure3 implements BinaryOperator < Integer > {
private final ClosurePrincipleTest arg$1 ;
public MyClosure3 ( ClosurePrincipleTest arg $ 1 ) {
this . arg$1 = arg$1;
}
@ Override
public Integer apply ( Integer a , Integer b ) {
return this . arg$1 . lambda$test$$$ (a, b);
}
}
生成的方法依旧是两个参数,方法内部可以通过 this.成员变量 的形式访问到目标成员变量,但它不再是静态方法,而是成员方法。
生成的类又发生了变化,它的构造函数需要接收一个成员变量所在类的实例,重写的方法中也不再调用生成的静态方法,而是成员方法。
10. 流的构造与拆分
调用集合的 stream() 方法时,其底层调用了一个静态方法:
1 2 3 default Stream < E > stream () {
return StreamSupport . stream ( spliterator (), false );
}
这个方法内部又调用了 spliterator():
1 2 3 4 @ Override
default Spliterator < E > spliterator () {
return Spliterators . spliterator ( this , 0 );
}
调用这个方法可以获取一个 Spliterator 实例,那它有什么用呢?
它可以消费下一个元素,也可以消费剩余元素:
1 2 3 4 5 6 7 8 9 10 11 12 13 List < Integer > list = List . of ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 );
@ Test
public void testTryAdvance () {
Spliterator < Integer > spliterator = list . spliterator ();
// 消费下一个元素
spliterator . tryAdvance (i -> assertThat (i). isEqualTo ( 1 ));
spliterator . tryAdvance (i -> assertThat (i). isEqualTo ( 2 ));
spliterator . tryAdvance (i -> assertThat (i). isEqualTo ( 3 ));
// 消费剩余元素
spliterator . forEachRemaining (i -> assertThat (i). isGreaterThanOrEqualTo ( 4 ). isLessThanOrEqualTo ( 9 ));
}
它也可以对元素进一步拆分:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @ Test
public void testTrySplit () {
Spliterator < Integer > sp1 = list . spliterator ();
// 切一半
Spliterator < Integer > sp2 = sp1 . trySplit ();
// 对 sp2 再切一半
Spliterator < Integer > sp3 = sp2 . trySplit ();
// [5, 9]
sp1 . forEachRemaining (i -> assertThat (i). isGreaterThanOrEqualTo ( 5 ). isLessThanOrEqualTo ( 9 ));
// [3, 4]
sp2 . forEachRemaining (i -> assertThat (i). isGreaterThanOrEqualTo ( 3 ). isLessThanOrEqualTo ( 4 ));
// [1, 2]
sp3 . forEachRemaining (i -> assertThat (i). isGreaterThanOrEqualTo ( 1 ). isLessThanOrEqualTo ( 2 ));
}
这拆分有什么用呢?
在多线程环境下,可以将集合拆分成若干个小集合,将它们交给不同的线程处理,最后再合并处理结果,以提高处理效率。
当然,实际使用时并不需要用户手动拆分,调用 Stream 中的 parallel() 方法就可以轻松完成拆分与合并。