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

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个回答

9

这不是对你问题的回答,但我认为这是解决你问题的方法。接口 org.springframework.data.repository.CrudRepository 确实有返回 java.lang.Iterable 类型的方法,但你不应该使用这个接口。取而代之的是使用子接口,例如在你的情况下使用 org.springframework.data.mongodb.repository.MongoRepository。这个接口有返回类型为 java.util.List 的方法。


3
我会推荐使用通用的CrudRepository,以避免将代码绑定到具体实现上。 - stanlick

8
我将使用我的自定义工具来转换已有的集合(如果可用)。 主要代码:
public static <T> Collection<T> toCollection(Iterable<T> iterable) {
    if (iterable instanceof Collection) {
        return (Collection<T>) iterable;
    } else {
        return Lists.newArrayList(iterable);
    }
}

理想情况下,上述内容应使用ImmutableList,但ImmutableCollection不允许包含null值,这可能会导致不良结果。
测试:
@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));
}

我为所有类型的集合(集合,列表等)实现了类似的实用程序。 我认为这些应该已经成为Guava的一部分,但我没有找到它。


1
你一年前的回答成为了一个新问题的基础,吸引了很多浏览和评论。https://dev59.com/llwY5IYBdhLWcg3wgHzy - Paul Boddington

6

由于RxJava是一种工具,而这看起来就像一个钉子,因此您可以执行以下操作:

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

28
有没有办法让 jQuery 参与其中? - Dmitry Minkovsky
3
如果在 RxJava 中存在空项,它就会崩溃。是这样吗? - MBH
我相信RxJava2不允许空项,在RxJava中应该没问题。 - DariusL

6
在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 答案的启发。

6
一旦调用containscontainsAllequalshashCoderemoveretainAllsizetoArray,都必须遍历元素。
如果你只偶尔调用isEmptyclear等方法,那么最好是懒加载集合。例如,你可以使用支持的ArrayList来存储先前迭代的元素。
我不知道任何库中是否有这样的类,但编写起来应该非常简单。

1
Tada.. https://github.com/soluvas/soluvas-framework/commit/34b22c3f20b9d55f061d2ef8c539efb995af15d6 :) - Hendy Irawan

4
这是一个使用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);
    }
}

3

两点说明

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

1
尝试使用来自CactoosStickyList
List<String> list = new StickyList<>(iterable);

1

虽然来晚了,但我创建了一个非常优雅的Java 8解决方案,允许将任何类型的Iterable转换为任何类型的Collection,无需使用任何库:

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

0
如果您能升级到Spring Data 3,这个问题已经得到解决。有一个新的接口ListCrudRepository可以完全满足您的需求。
以下是来自https://spring.io/blog/2022/02/22/announcing-listcrudrepository-friends-for-spring-data-3-0的接口:
public interface ListCrudRepository<T, ID> extends CrudRepository<T, ID> {
    <S extends T> List<S> saveAll(Iterable<S> entities);
    List<T> findAll();
    List<T> findAllById(Iterable<ID> ids);
}

在第3版中,您必须实现两个接口

所以在第2版中:

public interface PersonRepository<Person, Long> extends 
   PagingAndSortingRepository<Person, Long> {}

在第三个版本中应该进行更改:

public interface PersonRepository<Person, Long> extends
    PagingAndSortingRepository<Person, Long>,ListCrudRepository<Person, Long> {}

其他更改在https://spring.io/blog/2022/02/22/announcing-listcrudrepository-friends-for-spring-data-3-0中提到。


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