简单的方法转换Iterable到集合

在我的应用程序中,我使用第三方库(Spring Data for MongoDB准确地说)。

这个库的方法返回Iterable<T>,而我的其余代码期望Collection<T>

有没有什么实用的方法可以让我快速地把一个转换成另一个?为了这么简单的事情,我希望避免在代码中创建一堆foreach循环。

390388 次浏览

一旦你调用containscontainsAllequalshashCoderemoveretainAllsizetoArray,你无论如何都必须遍历元素。

如果你偶尔只调用isEmptyclear这样的方法,我认为你最好是惰性地创建集合。例如,你可以有一个备份ArrayList来存储先前迭代的元素。

我不知道任何一个库中有这样的类,但是编写它应该是一个相当简单的练习。

对于番石榴,你可以使用Lists.newArrayList (Iterable)Sets.newHashSet (Iterable),以及其他类似的方法。这当然会将所有元素复制到内存中。如果这是不可接受的,我认为你的代码与这些工作应该采取Iterable而不是Collection。Guava碰巧也提供了一些方便的方法,让你可以使用Iterable(比如Iterables.isEmpty(Iterable)Iterables.contains(Iterable, Object))在Collection上做一些事情,但性能影响更为明显。

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());
}
}

你也可以编写自己的实用方法:

public static <E> Collection<E> makeCollection(Iterable<E> iter) {
Collection<E> list = new ArrayList<E>();
for (E item : iter) {
list.add(item);
}
return list;
}

尽管如此,不要忘记所有的集合都是有限的,而Iterable没有任何承诺。如果某个东西是Iterable,你可以得到一个Iterator,就是这样。

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

将扩大到:

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

it.hasNext()不需要返回false。因此,在一般情况下,您不能期望能够将每个Iterable转换为一个集合。例如,你可以遍历所有正自然数,遍历带有循环的东西,反复产生相同的结果,等等。

除此之外:Atrey的回答很好。

两个评论

    使用foreach不需要将Iterable转换为Collection Iterable可以直接用在这样的循环中,没有 语法上的差异,所以我几乎不明白为什么要问原来的问题
  1. 将Iterable转换为Collection的建议方法是不安全的(与CollectionUtils相同)-不能保证后续调用next()方法会返回不同的对象实例。此外,这种担忧并不纯粹是理论上的。例如,用于将值传递给Hadoop Reducer的reduce方法的可迭代实现总是返回相同的值实例,只是字段值不同。因此,如果从上面应用makeCollection(或CollectionUtils.addAll(Iterator)),您将得到一个包含所有相同元素的集合。

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

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

4.0版本(目前在SNAPSHOT中)支持泛型,您可以摆脱@SuppressWarnings

更新:从Cactoos中检查IterableAsList

这不是对你问题的回答,但我相信这是解决你问题的办法。接口org.springframework.data.repository.CrudRepository确实有返回java.lang.Iterable的方法,但是你不应该使用这个接口。相反,使用子接口,在你的例子中是org.springframework.data.mongodb.repository.MongoRepository。该接口具有返回java.util.List类型对象的方法。

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

在Java 8中,你可以这样做,将Iterable中的所有元素添加到Collection中,并返回它:

public static <T> Collection<T> iterableToCollection(Iterable<T> iterable) {
Collection<T> collection = new ArrayList<>();
iterable.forEach(collection::add);
return collection;
}

灵感来自@Afreys的回答。

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();
}

我使用我的自定义实用程序强制转换现有的集合(如果可用)。

主要:

public static <T> Collection<T> toCollection(Iterable<T> iterable) {
if (iterable instanceof Collection) {
return (Collection<T>) iterable;
} else {
return Lists.newArrayList(iterable);
}
}

理想情况下,上面将使用immutabelist,但ImmutableCollection不允许空值,这可能会产生不希望看到的结果。

测试:

@Test
public void testToCollectionAlreadyCollection() {
ArrayList<String> list = Lists.newArrayList(FIRST, MIDDLE, LAST);
assertSame("no need to change, just cast", list, toCollection(list));
}


@Test
public void testIterableToCollection() {
final ArrayList<String> expected = Lists.newArrayList(FIRST, null, MIDDLE, LAST);


Collection<String> collection = toCollection(new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return expected.iterator();
}
});
assertNotSame("a new list must have been created", expected, collection);
assertTrue(expected + " != " + collection, CollectionUtils.isEqualCollection(expected, collection));
}

我为集合的所有子类型(Set,List等)实现了类似的实用程序。我以为这些已经是番石榴的一部分了,但我还没找到。

在JDK 8+中,不使用任何额外的库:

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

编辑:上面的是Iterator。如果你处理的是Iterable

iterable.forEach(target::add);

因为RxJava是一个锤子,而这个看起来像钉子,你可以这样做

Observable.from(iterable).toList().toBlocking().single();

下面是一个在Java 8中实现这一功能的SSCCE

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.stream.Collectors;
import java.util.stream.IntStream;


public class IterableToCollection {
public interface CollectionFactory <T, U extends Collection<T>> {
U createCollection();
}


public static <T, U extends Collection<T>> U collect(Iterable<T> iterable, CollectionFactory<T, U> factory) {
U collection = factory.createCollection();
iterable.forEach(collection::add);
return collection;
}


public static void main(String[] args) {
Iterable<Integer> iterable = IntStream.range(0, 5).boxed().collect(Collectors.toList());
ArrayList<Integer> arrayList = collect(iterable, ArrayList::new);
HashSet<Integer> hashSet = collect(iterable, HashSet::new);
LinkedList<Integer> linkedList = collect(iterable, LinkedList::new);
}
}

试试Cactoos中的StickyList:

List<String> list = new StickyList<>(iterable);

你可以使用Eclipse集合工厂:

Iterable<String> iterable = Arrays.asList("1", "2", "3");


MutableList<String> list = Lists.mutable.withAll(iterable);
MutableSet<String> set = Sets.mutable.withAll(iterable);
MutableSortedSet<String> sortedSet = SortedSets.mutable.withAll(iterable);
MutableBag<String> bag = Bags.mutable.withAll(iterable);
MutableSortedBag<String> sortedBag = SortedBags.mutable.withAll(iterable);

你也可以将Iterable转换为LazyIterable,并使用转换器方法或任何其他可用的api。

Iterable<String> iterable = Arrays.asList("1", "2", "3");
LazyIterable<String> lazy = LazyIterate.adapt(iterable);


MutableList<String> list = lazy.toList();
MutableSet<String> set = lazy.toSet();
MutableSortedSet<String> sortedSet = lazy.toSortedSet();
MutableBag<String> bag = lazy.toBag();
MutableSortedBag<String> sortedBag = lazy.toSortedBag();

以上所有Mutable类型都扩展了java.util.Collection

注意:我是Eclipse Collections的提交者。

我在试图获取__abc1的List时遇到了类似的情况,而不是在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();
}

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

有点晚了,但我创建了一个非常优雅的Java 8解决方案,允许将T的Iterable转换为T的任何集合,而不需要任何库:

public static <T, C extends Collection<T>> C toCollection(Iterable<T> iterable, Supplier<C> baseSupplier)
{
C collection = baseSupplier.get();
    

iterable.forEach(collection::add);
    

return collection;
}

使用的例子:

Iterable<String> iterable = ...;
List<String> list = toCollection(iterable, ArrayList::new);

当你从Spring Data中获得Iterable时,你有两个额外的选择。

  1. 你可以用一个返回ListSetStreamable的版本来重写存储库中返回Iterable的方法。通过这种方式,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特定的类型,这是许多用户不喜欢的。

有一个章节中关于这一点的参考文档