我有Collection
个任意类的元素。我想迭代遍历集合并使用元素和集合中的每个其他元素逐个执行一些操作(不包括元素本身)。为简单起见,请设为List<Integer>
:
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
使用for
循环,它将是:
for (Integer i : list) {
for (Integer j : list) {
if (!i.equals(j)) System.out.println(i * 2 + j); //just for example
}
}
问题是如何使用Stream API?
这就是我所得到的:
list.stream().forEach(i ->
list.stream().forEach(j -> {
if (!i.equals(j)) System.out.println(i * 2 + j);
})
);
它看起来并不比嵌套循环更好。有更优雅的方式吗?
答案 0 :(得分:14)
您可以使用flatMap
操作执行此操作:
list.stream()
.flatMap(i -> list.stream().filter(j -> !i.equals(j)).map(j -> i * 2 + j))
.forEach(System.out::println);
此代码正在创建输入列表的Stream。它使用同一列表创建的流平面映射列表的每个元素,其中当前元素被过滤掉,并且此新列表的每个元素都是i * 2 + j
操作的结果。
然后将所有元素打印到控制台。
答案 1 :(得分:6)
您可以通过事先映射到int
值来避免对象比较,例如
list.stream().mapToInt(Integer::intValue)
.flatMap(i -> list.stream().filter(j -> j!=i).mapToInt(j -> i*2 + j))
.forEach(System.out::println);
但实际上,您正在对不变的东西执行条件运算和相等检查。由于整个操作依赖于源列表而不改变其中间的内容,因此您可以首先简单地生成配对列表索引:
final int end=list.size();
IntStream.range(0, end).flatMap(i ->
IntStream.concat(IntStream.range(0, i), IntStream.range(i+1, end))
.map(j -> list.get(i) * 2 + list.get(j)))
.forEach(System.out::println);
如果您不想计算数值但是为对创建对象,由于IntStream
没有flatMapToObj
操作,它会变得有点复杂,所以我们需要mapToObj
和flatMap
的组合(除非我们使用boxed
)。因此,构建一个双长度数组示例如下:
IntStream.range(0, end).mapToObj(i ->
IntStream.concat(IntStream.range(0, i), IntStream.range(i+1, end))
.mapToObj(j -> new int[]{ list.get(i), list.get(j)}))
.flatMap(Function.identity())
.forEach(a -> System.out.println(a[0]*2+a[1]));
当然,对于像[ 1, 2, 3, 4, 5 ]
这样简单的列表,我们可以首先使用IntStream.rangeClosed(1, 5)
,而无需处理List
:
IntStream.rangeClosed(1, 5).flatMap(i ->
IntStream.concat(IntStream.range(1, i), IntStream.rangeClosed(i+1, 5))
.map(j -> i*2 + j))
.forEach(System.out::println);