Java 8中使用流时的规则

首先,让我们对有一个基本的了解。 然后,我们将研究在使用流时可能发生的副作用。

流表示来自源的一系列对象,支持聚合操作。 在使用流时要通知的一件事是,对聚合操作(中间操作)进行了延迟评估,即它们直到终端操作开始才开始处理流的内容。 这使Java编译器和运行时能够优化它们如何处理流。

使用Java 8,Collection接口具有两种生成Stream的方法。

  1. stream() -返回一个以集合为源的顺序流。

下面给出了一个非常基本的示例,其中小于5的double类型的数组元素被转换为整数。 由于我们正在使用流,因此接收到的输出顺序是固定的。

[代码语言=“ java”]

/ **
*流的基本示例
* /
Double [] doubleArray = {1.8,2.0,3.2,4.5,5.6,6.0,7.0,8.9};
List listOfDouble =
新的ArrayList (Arrays.asList(doubleArray));

listOfDouble
。流()
.filter(element-> element value.intValue())。
forEach(System.out :: println);

[/码]

  1. parallelStream() -返回一个以集合为源的并行Stream。

并行计算包括将一个问题分解为多个子问题,同时解决这些问题(并行处理,每个子问题在单独的线程中运行),然后将解决方案的结果组合到子问题中。 当流并行执行时,Java运行时将流划分为多个子流。 聚合操作迭代并并行处理这些子流,然后合并结果。

现在,在下面的示例中,我们使用并行流而不是流。 元素的评估顺序可以在每次运行时变化。

[代码语言=“ java”]

/ **
*并行流的基本示例
* /
Double [] doubleArray = {1.8,2.0,3.2,4.5,5.6,6.0,7.0,8.9};
List listOfDouble =
新的ArrayList (Arrays.asList(doubleArray));

listOfDouble
.parallelStream()
.filter(element-> element value.intValue())。
forEach(System.out :: println);

[/码]

副作用 :

  1. 干扰

谈到副作用,流操作中的Lambda表达式不应产生干扰 。 在管道处理流时修改流的源时会发生干扰。

[代码语言=“ java”]

/ **
*干扰示例
*这将通过ConcurrentModificationException
* /
尝试{
List listOfStrings =
新的ArrayList (Arrays.asList(“ one”,“ two”,“ four”)));

listOfStrings
.parallelStream()
.map(s-> {
listOfStrings.add(“三”);
返回listOfStrings;
})
.forEach(System.out :: println);
} catch(Exception e){
System.out.println(“捕获到的异常:“ + e.toString());
}

[/码]

上面的示例导致ConcurrentModificationException。 即使我们将使用流而不是并行流,也会发生相同的情况。 这种行为的原因是懒惰的评估。 这意味着此示例中的管道在调用操作get时开始执行,并在get操作完成时结束执行。 在管道执行期间尝试修改流源时,它将引发运行时异常。

  1. 有状态Lambda表达式

有状态lambda表达式是一种有状态的lambda表达式,其结果取决于在管道执行期间可能更改的任何状态。 对以下示例的理解将使您对该定义有清楚的了解。

[代码语言=“ java”]

/ **
*有状态lambda表达式示例
* /
Integer [] intArray = {1,2,3,4,5,6,7,8};
List listOfIntegers =
新的ArrayList (Arrays.asList(intArray));

List serialStorage =新的ArrayList ();

System.out.println(“串行流:”);
listOfIntegers
。流()
//它使用有状态的lambda表达式。 不应该这样做!
.map(e-> {
serialStorage.add(e);
返回e;
})
.forEachOrdered(e-> System.out.print(e +“”));
System.out.println(“”);

串行存储
。流()
.forEachOrdered(e-> System.out.print(e +“”));
System.out.println(“”);

System.out.println(“并行流:”);
List parallelStorage = Collections.synchronizedList(
new ArrayList ());
listOfIntegers
.parallelStream()
//它使用有状态的lambda表达式。 不应该这样做!
.map(e-> {
parallelStorage.add(e);
返回e;
})
.forEachOrdered(e-> System.out.print(e +“”));
System.out.println(“”);

并行存储
。流()
.forEachOrdered(e-> System.out.print(e +“”));
System.out.println(“”);
}

[/码]

Lambda表达式e-> {parallelStorage.add(e); 返回e; }是有状态的lambda表达式。 每次运行代码时,其结果可能会有所不同。 本示例打印以下内容:

串行流:
8 7 6 5 4 3 2 1
8 7 6 5 4 3 2 1
并行流:
8 7 6 5 4 3 2 1
1 3 6 2 4 5 8 7

无论流是以串行还是并行方式执行,forEachOrdered的操作均按流指定的顺序处理元素。 但是,要记住的一点是,当并行执行流时,映射操作将处理Java运行时和编译器指定的流元素。 因此,lambda表达式e-> {parallelStorage.add(e); 返回e; 每次向代码添加元素时,parallelStorage可能会有所不同。 为了获得确定性和可预测的结果,请确保流操作中的lambda表达式参数不是有状态的。

注意:流的最重要特征之一是它们只能运行一次,即不允许流的可重用性。

参考文献:

Java™教程

Java编程语言的教程和参考指南

docs.oracle.com