Suppose I have a list like this :
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Is it possible to use a Java 8 stream to take every second element from this list to obtain the following?
[1, 3, 5, 7, 9]
Or maybe even every third element?
[1, 4, 7, 10]
Basically, I'm looking for a function to take every nth element of a stream:
List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List list2 = list.stream().takenth(3).collect(Collectors.toList());
System.out.println(list2);
// => [1, 4, 7, 10]
解决方案
One of the prime motivations for the introduction of Java streams was to allow parallel operations. This led to a requirement that operations on Java streams such as map and filter be independent of the position of the item in the stream or the items around it. This has the advantage of making it easy to split streams for parallel processing. It has the disadvantage of making certain operations more complex.
So the simple answer is that there is no easy way to do things such as take every nth item or map each item to the sum of all previous items.
The most straightforward way to implement your requirement is to use the index of the list you are streaming from:
List list = ...;
return IntStream.range(0, list.size())
.filter(n -> n % 3 == 0)
.mapToObj(list::get)
.collect(Collectors.toList());
A more complicated solution would be to create a custom collector that collects every nth item into a list.
class EveryNth {
private final int nth;
private final List> lists = new ArrayList<>();
private int next = 0;
private EveryNth(int nth) {
this.nth = nth;
IntStream.range(0, nth).forEach(i -> lists.add(new ArrayList<>()));
}
private void accept(C item) {
lists.get(next++ % nth).add(item);
}
private EveryNth combine(EveryNth other) {
other.lists.forEach(l -> lists.get(next++ % nth).addAll(l));
next += other.next;
return this;
}
private List getResult() {
return lists.get(0);
}
public static Collector> collector(int nth) {
return Collector.of(() -> new EveryNth(nth),
EveryNth::accept, EveryNth::combine, EveryNth::getResult));
}
This could be used as follows:
List list = Arrays.asList("Anne", "Bill", "Chris", "Dean", "Eve", "Fred", "George");
list.stream().parallel().collect(EveryNth.collector(3)).forEach(System.out::println);
Which returns the result you would expect.
This is a very inefficient algorithm even with parallel processing. It splits all items it accepts into n lists and then just returns the first. Unfortunately it has to keep all items through the accumulation process because it's not until they are combined that it knows which list is the nth one. Given its complexity and inefficiency I would definitely recommending sticking with the indices based solution above in preference to this.