将可迭代对象转换为集合的简便方法

491
在我的应用程序中,我使用第三方库(具体来说是Spring Data for MongoDB)。
该库的方法返回Iterable<T>,而我的其他代码期望Collection<T>
是否有任何实用方法可以让我快速地将一个转换为另一个?我希望避免在我的代码中创建大量的foreach循环来处理这么简单的事情。

4
任何执行此操作的实用方法都必须对集合进行迭代,因此您不能期望获得任何性能提升。但是,如果您只是想寻找语法糖,我建议使用Guava或者Apache Collections。 - Sebastian Ganslandt
is bound to iterate of the collection anyway” -- 不是的。请查看我的答案了解详情。 - aioobe
5
在你的特定用例中,你可以通过扩展CrudRepository接口并添加返回Collection<T> / List<T> / Set<T>(根据需要)而不是Iterable<T>的方法来实现。 - Kevin Van Dyck
21个回答

415

JDK 8及以上版本,不使用任何额外的库:

Iterator<T> source = ...;
List<T> target = new ArrayList<>();
source.forEachRemaining(target::add);

编辑:上面的内容是针对Iterator。如果您要处理Iterable

iterable.forEach(target::add);

101
或者 iterable.forEach(target::add); 的意思是对可迭代对象中的每个元素都执行 target.add() 方法。 - Cephalopod

406

使用Guava,您可以使用Lists.newArrayList(Iterable)Sets.newHashSet(Iterable)等类似方法。当然,这将复制所有元素到内存中。如果不接受这种情况,我认为与这些一起工作的代码应该使用Iterable而不是Collection。 Guava还提供了方便的方法来执行可以使用Iterable执行的Collection上的操作(例如Iterables.isEmpty(Iterable)Iterables.contains(Iterable, Object)),但性能影响更为明显。


1
它是否直接迭代所有元素?即Lists.newArrayList(Iterable).clear()是线性时间还是常数时间操作? - aioobe
2
@aioobe:它创建可迭代对象的副本。并没有指定需要视图,鉴于大多数Collection上的方法不能为Iterable的视图实现或不太高效,我认为这样做没有多少意义。 - ColinD
@ColinD 如果我想要一个视图怎么办?实际上,我想要的是将源集合与另一个元素连接起来的集合视图。我可以使用Iterables.concat(),但它只提供了一个Iterable,而不是一个Collection :( - Hendy Irawan
1
这是我的问题:https://dev59.com/zm445IYBdhLWcg3wXZIy。不幸的是,不能解决问题的简单答案是使用`Iterables.concat()`。更长的答案提供了`Collection`...我想知道为什么这不是更常见的支持? - Hendy Irawan

112

使用Java 8中的java.util.stream提供简洁的解决方案:

public static <T> List<T> toList(final Iterable<T> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false)
                        .collect(Collectors.toList());
}

自Java 16开始,您可以使用Stream.toList()

public static <T> List<T> toList(final Iterable<T> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false)
                        .toList();
}

1
commons-collections中的IteratorUtils相比,这种方法太慢了。 - Alex Burdusel
3
有多慢?IteratorUtils.toList()以类似Java 5之前的方式使用迭代器,逐个将元素添加到新创建的列表中。这种方法简单且可能是最快的,但会使二进制文件增加734 kB大小,如果你认为这种方法是最好的,你可以自己实现它。 - xehpuk
10
我进行了一个简单的基准测试,得出有时第一个更快,有时第二个更快的结论。请展示你的基准测试。 - xehpuk
3
这个问题可能会成为新的被接受的答案 - 避免使用过多的库(如Guava)是很好的。 - java-addict301

101
您也可以编写自己的实用程序方法:
public static <E> Collection<E> makeCollection(Iterable<E> iter) {
    Collection<E> list = new ArrayList<E>();
    for (E item : iter) {
        list.add(item);
    }
    return list;
}

34
如果唯一的问题是从“Iterable”转换为“Collection”,我更喜欢这种方法,而不是导入一个庞大的第三方集合库。 - aioobe
2
4行函数代码比 2MB 编译库代码更可取,因为其中 99% 的代码都未被使用。另外还有一项成本:许可证复杂性。Apache 2.0 许可证非常灵活,但也不免繁琐的规定。理想情况下,我们希望这些常见模式中的 一些 能够直接集成到 Java 运行时库中。 - Coder Guy
2
还有一点,既然你已经在使用ArrayList了,为什么不直接使用协变的List类型呢?这样可以满足更多的合约,而无需进行向下转型或重新组合,而且Java也不支持较低的类型边界。 - Coder Guy
@JonathanNeufeld 或者为什么不直接返回一个 ArrayList<T>? - Juan
5
@Juan 因为这不是很SOLID。ArrayList暴露了大多数情况下都是不必要的实现细节(YAGNI),这违反了单一职责和依赖反转原则。我会将其保留为List,因为它展示了比Collection更多的内容,同时仍然完全符合SOLID原则。如果你担心使用INVOKEINTERFACE相对于INVOKEVIRTUAL在JVM性能上的影响,那么大量基准测试表明,这并不值得失眠。 - Coder Guy
为什么不使用LinkedList?如果数组的大小未定义,ArrayList是低效的。LinkedList的添加方法复杂度为O(1),而ArrayList的添加方法复杂度在最坏情况下为O(N)。为了增长,ArrayList会复制所有先前的数组,并创建一个两倍大小的新ArrayList - Michał Stochmal

53

IteratorUtils 可能会有所帮助(尽管它们在最新的稳定版本3.2.1中不支持泛型)来自 commons-collections

@SuppressWarnings("unchecked")
Collection<Type> list = IteratorUtils.toList(iterable.iterator());

目前处于SNAPSHOT版本的版本4.0已经支持泛型,您可以摆脱@SuppressWarnings

更新:请查看来自CactoosIterableAsList


5
自4.1版本以来,还新增了一个IterableUtils.toList(Iterable)方法,它是一个便捷方法,底层使用了IteratorUtils,但与IteratorUtils.toList不同的是,它也具有空值安全性。 - Yoory N.

38
当你从Spring Data获取你的`Iterable`时,你有几个额外的选择。
1. 你可以重写返回`Iterable`的方法,改为返回`List`、`Set`或者Streamable版本。这样Spring Data就会为你完成转换。 2. 你可以在你的仓库超级接口中实现此操作,这样你就不必在所有的仓库接口中都进行重写。 3. 如果你使用Spring Data JPA,那么这个过程已经在`JpaRepository`中为你完成了。 4. 你也可以使用刚才提到的`Streamable`自己进行转换:
Iterable<X> iterable = repo.findAll();
List<X> list = Streamable.of(iterable).toList();

既然您提到感到不满,也许为决定使用Iterable提供一些背景信息可能会有所帮助。

  1. 实际上,预计很少需要Collection,因此在许多情况下,这不应该产生影响。
  2. 使用重写机制可以返回不同的类型,而这在像Collection这样更具体的返回类型中是不可能的。这将使得无法返回Streamable,而Streamable适用于存储在获取所有元素之前可能返回结果的情况。
  3. Streamable实际上将是一个灵活的返回类型,因为它提供了易于转换为ListSetStream并且本身就是一个Iterable。但是,这将要求您在应用程序中使用Spring Data特定类型,这可能不受许多用户的欢迎。

参考文档中有关于此的章节说明


你让我爱上了JPA ;) - l33tHax0r

25

来自 CollectionUtils:

List<T> targetCollection = new ArrayList<T>();
CollectionUtils.addAll(targetCollection, iterable.iterator())

这里是该实用方法的完整源代码:

public static <T> void addAll(Collection<T> collection, Iterator<T> iterator) {
    while (iterator.hasNext()) {
        collection.add(iterator.next());
    }
}

它是否直接迭代所有元素?即Lists.newArrayList(someIterable).clear()是线性时间还是常数时间操作? - aioobe
我添加了addAll的源代码,顾名思义,它会将迭代器的值一个接一个地复制;它创建的是副本而不是视图。 - Tomasz Nurkiewicz
很遗憾,在CollectionUtils中没有一种方法可以跳过在额外的行中创建集合。 - Kalle Richter
损坏的链接 ☝️☝️ - Hola Soy Edu Feliz Navidad

16
我在尝试获取Project列表时遇到了类似的情况,而不是在CrudRepository接口中声明的默认Iterable<T> findAll()。因此,在我的ProjectRepository接口(它继承自CrudRepository)中,我只是声明了findAll()方法返回一个List<Project>而不是Iterable<Project>
package com.example.projectmanagement.dao;

import com.example.projectmanagement.entities.Project;
import org.springframework.data.repository.CrudRepository;
import java.util.List;

public interface ProjectRepository extends CrudRepository<Project, Long> {

    @Override
    List<Project> findAll();
}

我认为这是最简单的解决方案,而不需要转换逻辑或使用外部库。


15

我经常使用FluentIterable.from(myIterable).toList()


12
需要注意的是,这也是来自于番石榴。 - Vadzim
或者使用org.apache.commons.collections4库,然后使用FluentIterable.of(myIterable).toList()方法。 - du-it

14

在此期间,不要忘记所有集合都是有限的,而Iterable则没有任何承诺。如果某个东西是Iterable,那么你可以获取一个Iterator,就这样。

for (piece : sthIterable){
..........
}

将会被扩展为:

Iterator it = sthIterable.iterator();
while (it.hasNext()){
    piece = it.next();
..........
}

it.hasNext()不一定会返回false。因此,在一般情况下,您不能指望能够将每个Iterable转换为Collection。例如,您可以迭代所有正自然数、迭代在其中产生重复结果的具有循环的内容等。

否则:Atrey的答案非常好。


1
有没有人在实践/实际代码中遇到过一个可迭代对象,它可以迭代无限的东西(比如答案中给出的自然数示例)?我认为这样的可迭代对象会在很多地方引起痛苦和困扰... :) - David
2
@David 虽然我不能在我的生产代码中具体指出无限迭代器,但我可以想到它们可能出现的情况。例如,一个视频游戏可能有一种技能,以上面的答案所建议的循环模式创建物品。虽然我没有遇到过任何无限迭代器,但我确实遇到过内存是真正问题的迭代器。我有迭代器遍历磁盘上的文件。如果我有一个完整的1TB磁盘和4GB的RAM,将我的迭代器转换为集合时很容易耗尽内存。 - radicaledward101

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