封面来源:本文封面来源于网络,如有侵权,请联系删除。

参考 黑马程序员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. 高阶函数

所谓高阶,就是指它是其他函数对象的使用者。

使用高阶函数,能够:

  1. 将通用、复杂的逻辑隐含在高阶函数内
  2. 将易变、未定的逻辑放在外部的函数对象中

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,这个函数式接口接收两个参数:

  1. 流中的元素
  2. 一个 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);
}

并行流执行逻辑:

并行流执行逻辑

  1. 数据量问题:数据量大时才建议使用 并行流
  2. 并行流使用的线程会无限增加吗?与 CPU 能处理的线程数有关
  3. 收尾的意义:转不可变集合、StringBuilderString 等将合并的数据再进行转换
  4. 是否线程不安全:安全

Collectorof() 方法进行了重载,增加了 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 种方案:

  1. Characteristics.CONCURRENT + Characteristics.UNORDERED + 线程安全的容器
  2. 默认 + 线程不安全的容器

2 种方案均有优劣,应根据实际情况选择:

  1. 创建容器的次数减少,占用内存较少,但在数据量巨大的情况下,线程竞争会降低程序的性能
  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);
}
}

在生成的类中,调用了在原始类中生成的静态方法。

7.2 LambdaMetafactory

那 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:动态方法类型,这里指的是生成类 MyLambdaapply() 方法对应的 MethodType

返回的 CallSite 对象又有什么用呢?

可以调用它的 getTarget() 方法获取到一个 MethodHandle 对象,表示用于创建函数对象的工厂方法的 MethodHandle,也就是创建 MyLambda 对象的工厂方法对应的 MethodHandle

那后续不就可以调用 MethodHandleinvoke() 方法获取到函数对象?

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. 成员变量

引用局部变量

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() 方法就可以轻松完成拆分与合并。