【问题标题】:Easy way to convert Iterable to Collection将 Iterable 转换为 Collection 的简单方法
【发布时间】:2011-09-18 23:46:48
【问题描述】:

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

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

是否有任何实用方法可以让我快速将一种转换为另一种?我想避免在我的代码中为这么简单的事情创建一堆foreach 循环。

【问题讨论】:

  • 任何用于执行操作的实用方法都必然会迭代集合,因此您不能指望任何性能提升。但如果你只是在寻找语法糖,我会选择 Guava 或 Apache Collections。
  • "无论如何都会迭代集合",——不,不是。详情见我的回答。
  • 在您的特定用例中,您可以使用自己的接口扩展 CrudRepository 并使用返回 Collection / List / Set (根据需要)而不是 Iterable 的方法

标签: java collections spring-data iterable


【解决方案1】:

尝试窄铸:(List<iterable_type>)iterable;

【讨论】:

    【解决方案2】:

    当您从 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 特定类型。

    有一个section about this in the reference documentation

    【讨论】:

    • 你让我在 JPA ;)
    【解决方案3】:

    参加聚会有点晚了,但我创建了一个非常优雅的 Java 8 解决方案,它允许将 T 的 Iterable 转换为 T 的任何 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);
    

    【讨论】:

      【解决方案4】:

      我在尝试获取Projects 的List 时遇到了类似的情况,而不是在CrudRepository 接口中声明的默认Iterable&lt;T&gt; findAll()。因此,在我的ProjectRepository 接口(从CrudRepository 扩展)中,我只是声明了findAll() 方法以返回List&lt;Project&gt; 而不是Iterable&lt;Project&gt;

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

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

      【讨论】:

        【解决方案5】:

        我没有看到没有任何依赖关系的简单的单行解决方案。 我简单使用

        List<Users> list;
        Iterable<IterableUsers> users = getUsers();
        
        // one line solution
        list = StreamSupport.stream(users.spliterator(), true).collect(Collectors.toList());
        

        【讨论】:

          【解决方案6】:

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

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

          编辑:以上是Iterator。如果你正在处理Iterable

          iterable.forEach(target::add);
          

          【讨论】:

          • iterable.forEach(target::add);
          【解决方案7】:

          您可以使用Eclipse Collections factory:

          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 的提交者。

          【讨论】:

            【解决方案8】:

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

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

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

            更新:检查 IterableAsListCactoos

            【讨论】:

            • 但这需要一个 Iterator,而不是 Iterable
            • @hithwen,我不明白——Iterable 提供了一个迭代器(如答案中所述)——有什么问题?
            • 不知道我在想什么^^U
            • 从 4.1 开始还有 IterableUtils.toList(Iterable),这是一种方便的方法,在底层使用 IteratorUtils,但也是 null 安全的(与 IteratorUtils.toList 不同)。
            【解决方案9】:

            Cactoos 尝试StickyList

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

            【讨论】:

              【解决方案10】:

              这是一个 SSCCE,它是在 Java 8 中实现此目的的好方法

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

              【讨论】:

                【解决方案11】:

                使用Guava,您可以使用Lists.newArrayList(Iterable)Sets.newHashSet(Iterable),以及其他类似方法。这当然会将所有元素复制到内存中。如果这是不可接受的,我认为你的代码应该使用Iterable 而不是Collection。 Guava 也恰好提供了方便的方法来执行您可以使用Iterable(例如Iterables.isEmpty(Iterable)Iterables.contains(Iterable, Object))在Collection 上执行的操作,但性能影响更明显。

                【讨论】:

                • 它是否直接遍历所有元素?即,Lists.newArrayList(Iterable).clear() 是线性还是恒定时间运算?
                • @aioobe:它创建了可迭代对象的副本。没有指定需要一个视图,并且鉴于Collection 上的大多数方法要么无法为Iterable 的视图实现,要么效率不高,这没有多大意义让我这样做。
                • @ColinD 如果我想要一个视图怎么办?实际上,我想要的是一个 Collection 视图,它是将源 Collection 与另一个元素附加的结果。我可以使用Iterables.concat(),但这给出了Iterable,而不是Collection :(
                • 这是我的问题:stackoverflow.com/questions/4896662/…。不幸的是,不能解决问题的简单答案是使用Iterables.concat()。更长的答案给出了Collection ...我想知道为什么不更普遍地支持它?
                【解决方案12】:

                来自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() 是线性还是恒定时间操作?
                • 我添加了addAll的源代码,顾名思义就是把迭代器的值一个接一个地复制过来;它创建一个副本而不是一个视图。
                • 很遗憾CollectionUtils 中没有方法可以在额外的一行中跳过创建集合。
                • 链接断开☝️☝️
                【解决方案13】:

                由于 RxJava 是一把锤子,而这有点像钉子,你可以这样做

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

                【讨论】:

                • 有没有办法让 jquery 参与进来?
                • 如果 RxJava 中有 null 项,它会崩溃。不是吗?
                • 我相信 RxJava2 不允许空项,在 RxJava 中应该没问题。
                【解决方案14】:

                如果可用,我会使用我的自定义实用程序来转换现有的集合。

                主要:

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

                理想情况下,上面会使用 ImmutableList,但 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));
                }
                

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

                【讨论】:

                【解决方案15】:

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

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

                【讨论】:

                • commons-collectionsIteratorUtils相比,这种方法太慢了
                • 慢多少? IteratorUtils.toList() 以 Java 5 之前的方式使用迭代器将元素一一添加到新创建的列表中。简单且可能最快,但会为您的二进制文件增加 734 kB,如果您发现此方法是最好的,您可以自己执行此操作。
                • 我做了一个原始的基准测试,得出的结论是有时第一个更快,有时第二个更快。向我们展示您的基准。
                • 这个问题可能是新接受的答案 - 避免过多的库(如 Guava)很好。
                【解决方案16】:

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

                【讨论】:

                  【解决方案17】:

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

                  【讨论】:

                  • 应该注意,它也是来自番石榴。
                  • 或来自 org.apache.commons.collections4。然后是 FluentIterable.of(myIterable).toList()
                  【解决方案18】:

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

                  【讨论】:

                  • 我会提倡使用通用 CrudRepository 以避免将您的代码绑定到具体实现。
                  【解决方案19】:

                  两句话

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

                  【讨论】:

                    【解决方案20】:

                    同时,不要忘记所有集合都是有限的,而 Iterable 没有任何承诺。如果某些东西是可迭代的,你可以得到一个迭代器,就是这样。

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

                    将扩展为:

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

                    it.hasNext() 不需要返回 false。因此,在一般情况下,您不能期望能够将每个 Iterable 转换为 Collection。例如,您可以迭代所有正自然数,迭代其中包含循环的东西,一遍又一遍地产生相同的结果,等等。

                    否则:Atrey 的回答很好。

                    【讨论】:

                    • 在实践/真实代码中,有没有人真正遇到过迭代无限事物(如答案中给出的自然数示例)的 Iterable?我认为这样的 Iterable 会在很多地方造成痛苦和困境...... :)
                    • @David 虽然我不能在我的任何生产代码中明确指出无限迭代器,但我可以想到它们可能发生的情况。视频游戏可能具有以上述答案建议的循环模式创建项目的技能。虽然我没有遇到任何无限迭代器,但我肯定遇到过内存是真正需要关注的迭代器。我对磁盘上的文件有迭代器。如果我有一个完整的 1TB 磁盘和 4GB 内存,我很容易用完内存,将我的迭代器转换为集合。
                    【解决方案21】:

                    您也可以为此编写自己的实用程序方法:

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

                    【讨论】:

                    • +1 如果从IterableCollection 是唯一的问题,我更喜欢这种方法而不是导入一个大型的第3 方收藏库。
                    • 4 行函数代码比 2 MB 的已编译库代码更可取,因为其中 99% 未使用。还有另一个成本:许可的复杂性。 Apache 2.0 许可证是灵活的,但并非没有一些繁琐的要求。理想情况下,我们会看到一些这些常见模式直接集成到 Java 运行时库中。
                    • 还有一点,既然您使用的是 ArrayList,为什么不简单地使用协变 List 类型呢?这允许您在不向下转换或重新组合的情况下满足更多合同,并且 Java 无论如何都不支持下限类型。
                    • @JonathanNeufeld 或者为什么不直接返回一个 ArrayList
                    • @Juan 因为那不是很SOLID。 ArrayList 暴露了很可能不必要的实现细节(YAGNI),这违反了单一职责和依赖倒置原则。我会将它留在 List 中,因为它确实比 Collection 在保持完全稳定的情况下暴露的多一点。如果您担心操作码 INVOKEINTERFACE 对 INVOKEVIRTUAL 的 JVM 性能影响,大量的基准测试表明不值得为此失眠。
                    【解决方案22】:

                    只要你调用containscontainsAllequalshashCoderemoveretainAllsizetoArray,无论如何你都必须遍历这些元素。

                    如果您偶尔只调用isEmptyclear 等方法,我想您最好懒惰地创建集合。例如,您可以有一个支持 ArrayList 来存储以前迭代的元素。

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

                    猜你喜欢
                    • 1970-01-01
                    • 2020-10-03
                    • 2012-09-02
                    • 2012-04-15
                    • 2011-10-08
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多