封面来源:本文封面来源于网络,如有侵权,请联系删除。
参考 黑马程序员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 ; Op op = b -> a + b; assertThat(op.op(1 )).isEqualTo(2 ); op = b -> NUMBER_INT + b; assertThat(op.op(1 )).isEqualTo(3 ); 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; } @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.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 @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); }; consumer.accept(1.0 , 2f , 3 ); Function<Double, Function<Float, Consumer<Integer>>> f = t -> u -> r -> { System.out.println(t + ";" + u + ";" + r); }; f.apply(1.0 ).apply(2f ).accept(3 ); Function<Double, BiConsumer<Float, Integer>> fun = t -> (u, r) -> { System.out.println(t + ";" + u + ";" + r); }; 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) -> { 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 @Test public void testTakeWhile () { List<Integer> list = Stream.of(1 , 2 , 3 , 7 , 8 , 9 , 4 , 5 , 6 ) .takeWhile(i -> i <= 6 ) .toList(); assertThat(list).containsExactly(1 , 2 , 3 ); } @Test public void testDropWhile () { List<Integer> list = Stream.of(1 , 2 , 3 , 7 , 8 , 9 , 4 , 5 , 6 ) .dropWhile(i -> i <= 6 ) .toList(); assertThat(list).containsExactly(7 , 8 , 9 , 4 , 5 , 6 ); }
4.3 流的生成
1 2 ThreadLocalRandom.current().ints(5 , 0 , 100 );
4.4 查找与判断
1 2 3 4 5 6 7 8 9 10 11 @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) { }
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(); if (!name.contains("lambda$test$" ) && !name.contains("test" )) { Assertions.fail(); } } } }
类和对象从哪来?
运行期间动态生成。
可以按需指定以下虚拟机参数,让生成的 class 文件保存到磁盘上:
1 2 3 4 5 -Djdk.internal.lambda.dumpProxyClasses -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 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 { }
先看下 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( lookup, "apply" , MethodType.methodType(BinaryOperator.class), MethodType.methodType(Object.class, Object.class, Object.class), impl, MethodType.methodType(Integer.class, Integer.class, Integer.class) ); MethodHandle factory = callSite.getTarget(); BinaryOperator<Integer> invoke = (BinaryOperator<Integer>) factory.invoke(); 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(); 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(); 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 ); 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 ); 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(); Spliterator<Integer> sp3 = sp2.trySplit(); sp1.forEachRemaining(i -> assertThat(i).isGreaterThanOrEqualTo(5 ).isLessThanOrEqualTo(9 )); sp2.forEachRemaining(i -> assertThat(i).isGreaterThanOrEqualTo(3 ).isLessThanOrEqualTo(4 )); sp3.forEachRemaining(i -> assertThat(i).isGreaterThanOrEqualTo(1 ).isLessThanOrEqualTo(2 )); }
这拆分有什么用呢?
在多线程环境下,可以将集合拆分成若干个小集合,将它们交给不同的线程处理,最后再合并处理结果,以提高处理效率。
当然,实际使用时并不需要用户手动拆分,调用 Stream
中的 parallel()
方法就可以轻松完成拆分与合并。