lambda 表达式注意事项 无状态和副作用
无状态
如果流操作的行为参数是有状态的,流管道结果可能是不确定的或不正确的。有状态lambda(或实现适当函数接口的其他对象)的结果取决于流管道执行期间可能更改的任何状态。有状态lambda的一个示例
Set<Integer> seen = Collections.synchronizedSet(new HashSet<>()); stream.parallel().map(e -> { if (seen.add(e)) return 0; else return e; })...
map在做对象映射的时候更改了seen的值,每次执行的seen都会更改其状态。
如果映射操作是并行执行的,由于线程调度的不同,相同输入的结果可能会因运行而不同,而对于无状态lambda表达式,结果总是相同的。
还请注意,尝试从行为参数访问可变状态会给您带来一个关于安全性和性能的错误选择;如果您不同步对该状态的访问,您将面临数据竞争,因此您的代码将被破坏,但如果您确实同步访问该状态,您有可能让争用破坏您正在寻求的好处的并行性。最好的方法是完全避免流操作的有状态行为参数;通常有一种方法可以重构流管道以避免有状态。
副作用
流操作的行为参数中的副作用通常是不可取的,因为它们通常会导致无意中违反无状态性要求,以及其他线程安全隐患。
如果行为参数确实有副作用,除非明确说明,否则不能保证:
- 这些副作用对其他线程的可见性;
- 在同一个流管道中对“相同”元素的不同操作在同一线程中执行;以及
- 行为参数总是被调用的,因为如果流实现能够证明它不会影响计算结果,那么流实现可以自由地从流管道中省略操作(或整个阶段)。
副作用1
List<String> l = Arrays.asList("A", "B", "C", "D"); long count = l.stream().peek(System.out::println).count();
其中count方法并不需要要将每个元素都遍历一遍,能获取结果 所以peek方法并不是每次都执行。
许多方法在使用的时候容易出现副作用 forEach
和 forEachOrdered
, 使用 println()方法打印日志也是不可取的
副作用2
ArrayList<String> results = new ArrayList<>(); stream.filter(s -> pattern.matcher(s).matches()) .forEach(s -> results.add(s)); // 不必要的副作用
而避免副作用的方式就是 使用Collectors
List<String>results = stream.filter(s -> pattern.matcher(s).matches()) .collect(Collectors.toList()); // 无副作用!