在Java 8中从java.util.stream.Stream中检索列表

497

我在尝试使用Java 8的lambda表达式来轻松过滤集合。但是我没有找到一种简洁的方式来在同一语句中将结果作为新列表检索出来。以下是目前我最简洁的方法:

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = new ArrayList<>();
sourceLongList.stream().filter(l -> l > 100).forEach(targetLongList::add);

网上的例子并没有回答我的问题,因为它们停止时没有生成新的结果列表。必须有一种更简明的方法。我本来期望 Stream 类拥有像 toList()toSet() 等方法。

是否有一种方法可以直接通过第三行分配变量 targetLongList


7
如果您之后不需要sourceLongList,可以使用Collection.removeIf(…)进行方便的操作。请注意,该方法可以移除满足特定条件的元素。 - Holger
3
这个怎么样?List<Long> targetLongList = sourceLongList.stream().collect(Collectors.toList()); - leeyuiwah
15个回答

668

只要你的流保持顺序,你正在做的可能是最简单的方法——否则你将不得不在 forEach 之前调用 sequential()

[后来编辑:之所以需要调用 sequential() 是因为代码(forEach(targetLongList::add))如果流是并行的,它将会有竞争条件。即使是在串行流中,forEach 也是明确的非确定性的,即元素处理的顺序不能保证。你需要使用 forEachOrdered 来确保正确的排序。Stream API 设计者的意图是在这种情况下使用收集器,如下所示。]

另一种选择是

targetLongList = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(Collectors.toList());

11
补充说明:我认为如果使用静态导入 toList,这段代码会变得更短、更清晰、更漂亮。可以在文件的导入部分加入以下内容实现:static import java.util.stream.Collectors.toList;。然后 collect 调用只需要写成 .collect(toList()) - Lii
4
在Eclipse中,可以通过在“首选项”->“Java”->“编辑器”->“内容辅助”->“收藏夹”中添加“Collectors”类来使IDE为方法添加静态导入。之后,只需键入“toLi”,并按下“Ctr+Space”组合键,IDE将填充“toList”并添加静态导入。 - Lii
5
需要记住的一件事是,IntStream和其他一些几乎但不完全是Stream的流没有collect(Collector)方法,您必须先调用IntStream.boxed()将它们转换为常规的Stream。然后再说,也许你只想要toArray() - Mutant Bob
为什么我们在使用forEach之前必须使用sequential()或者使用forEachOrdered - amarnath harish
1
因为forEach不能保证并行流的操作执行顺序。JavaDoc中说:“此操作的行为明确是不确定的。对于并行流管道,此操作不能保证尊重流的遇到顺序,因为这样做会牺牲并行性的好处。”(这段引文的第一句话实际上意味着顺序对于顺序流也没有保证,尽管在实践中它是被保留的。) - Maurice Naftalin
为什么不能像C#语法一样简单:(some ienumerable).ToList() - Lei Yang

195

更新:

另一种方法是使用Collectors.toList

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toList());

之前的解决方案:

另一种方法是使用 Collectors.toCollection

targetLongList = 
    sourceLongList.stream().
    filter(l -> l > 100).
    collect(Collectors.toCollection(ArrayList::new));

69
如果您想要特定的列表实现,这将是有用的。 - orbfish
1
尽管建议编写接口,但在某些情况下(其中之一是GWT),您必须针对具体实现进行编码(除非您希望将所有List实现编译并作为JavaScript交付)。 - Eduard Korenschi
4
这种方法的另一个优点,来自于Collectors::toList的Java文档:"返回的List可能在类型、可变性、可序列化性或线程安全性方面没有保证;如果需要更多对返回List的控制,请使用toCollection(Supplier)。" - Charles Wood

13

我喜欢使用一个实用方法,当我需要返回ArrayList的收集器时,它会返回给我。

我认为使用Collectors.toCollection(ArrayList::new)的解决方案对于这样常见的操作来说有点过于繁琐。

示例:

ArrayList<Long> result = sourceLongList.stream()
    .filter(l -> l > 100)
    .collect(toArrayList());

public static <T> Collector<T, ?, ArrayList<T>> toArrayList() {
    return Collectors.toCollection(ArrayList::new);
}

通过这个答案,我还想展示一下创建和使用自定义收集器的简单性,这在通常情况下非常有用。


如果您将结果声明为List<Long>,则无需使用此util方法。Collectors.toList就可以了。此外,使用特定的类而不是接口是一种代码异味。 - Lluis Martinez
2
@LluisMartinez:“Collectors.toList会做。”:不,很多情况下不行。因为如果您想在程序中稍后修改列表,则使用toList不是一个好主意。 toList文档说:“不能保证返回的List的类型、可变性、可序列化性或线程安全性;如果需要更多对返回的List的控制,请使用toCollection。”我的答案演示了一种在常见情况下使其更方便的方法。 - Lii
如果你想要特别创建一个ArrayList,那就可以。 - Lluis Martinez

13
Java 16 中有一个新的方法 Stream.toList()
List<Long> targetLongList = sourceLongList
         .stream()
         .filter(l -> l > 100)
         .toList();

6

5
一种更有效的方法(避免创建源列表和过滤器自动解包):
List<Long> targetLongList = LongStream.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L)
    .filter(l -> l > 100)
    .boxed()
    .collect(Collectors.toList());

5
如果您有一个基本类型的数组,您可以使用Eclipse Collections中提供的基本类型集合。
LongList sourceLongList = LongLists.mutable.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
LongList targetLongList = sourceLongList.select(l -> l > 100);

如果您无法更改List类型的sourceLongList

List<Long> sourceLongList = Arrays.asList(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
List<Long> targetLongList = 
    ListAdapter.adapt(sourceLongList).select(l -> l > 100, new ArrayList<>());

如果您想使用LongStream
long[] sourceLongs = new long[]{1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L};
LongList targetList = 
    LongStream.of(sourceLongs)
    .filter(l -> l > 100)
    .collect(LongArrayList::new, LongArrayList::add, LongArrayList::addAll);

注意:我是Eclipse Collections的贡献者。


1
如果您不介意使用第三方库,AOL的cyclops-react库(我是其中一名贡献者)拥有所有JDK Collection类型的扩展,包括List。ListX接口扩展了java.util.List并添加了大量有用的运算符,包括filter。
您只需简单地编写-
ListX<Long> sourceLongList = ListX.of(1L, 10L, 50L, 80L, 100L, 120L, 133L, 333L);
ListX<Long> targetLongList = sourceLongList.filter(l -> l > 100);

ListX也可以由现有的List创建(通过ListX.fromIterable)。

1
将可变列表收集起来:
targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toList());

将内容收集到不可变列表中:

targetList = sourceList.stream()
                       .filter(i -> i > 100) //apply filter
                       .collect(Collectors.toUnmodifiableList());

collect的解释来自于JavaDoc

使用Collector对流中的元素执行可变归约操作。Collector封装了作为collect(Supplier, BiConsumer, BiConsumer)参数使用的函数,允许重用收集策略并组合collect操作,例如多级分组或分区。如果流是并行的,并且Collector是并发的,并且流是无序的或Collector是无序的,则将执行并发归约(有关并发归约的详细信息,请参见Collector)。

这是一个终端操作。

在并行执行时,可能会实例化、填充和合并多个中间结果,以保持可变数据结构的隔离。因此,即使使用非线程安全的数据结构(如ArrayList)并行归约也不需要额外的同步。


1
LongStream类提供了另一种collect方法的变体,IntStream类和DoubleStream类也类似提供了该方法。
<R> R collect(Supplier<R> supplier,
              ObjLongConsumer<R> accumulator,
              BiConsumer<R,R> combiner)

在此流的元素上执行可变约简操作。可变约简是指约简值是可变结果容器(例如ArrayList),并且通过更新结果状态而不是替换结果来合并元素。这将产生与以下相当的结果:

R result = supplier.get();
  for (long element : this stream)
       accumulator.accept(result, element);
  return result;

与reduce(long, LongBinaryOperator)类似,collect操作可以在不需要额外同步的情况下并行化执行。这是一个终端操作。

使用此collect方法回答您的问题如下:

    LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, (list, value) -> list.add(value)
    , (list1, list2) -> list1.addAll(list2));

以下是方法引用的变体,它非常聪明,但有些棘手难懂:
     LongStream.of(1L, 2L, 3L, 3L).filter(i -> i > 2)
    .collect(ArrayList::new, List::add , List::addAll);

以下是 HashSet 变量的变体:
     LongStream.of(1L, 2L, 3L, 3).filter(i -> i > 2)
     .collect(HashSet::new, HashSet::add, HashSet::addAll);

同样,LinkedList的变体如下:
     LongStream.of(1L, 2L, 3L, 3L)
     .filter(i -> i > 2)
     .collect(LinkedList::new, LinkedList::add, LinkedList::addAll);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接