2011-06-20 33 views
270

在我的應用程序中,我使用第三方庫(準確地說是Spring Data for MongoDb)。簡單的方法來改變Iterable到集合

此庫的方法返回Iterable<T>,而其餘代碼預計爲Collection<T>

有什麼實用方法可以讓我快速將一個轉換爲另一個。我想避免在我的代碼中使用foreach循環來實現這樣一個簡單的事情。

+2

無論如何,執行操作的任何有用方法都必然會迭代集合,所以不能指望任何性能增益。但是如果你只是在尋找語法糖,我會選擇Guava或Apache Collections。 –

+0

「*無論如何都會綁定到集合的迭代*」, - 不,它不是。詳情請參閱我的回答。 – aioobe

+0

在你的具體用例,你可以擴大CrudRepository與你自己的接口與方法返回收集 /列表 /設置(根據需要),而不是可重用

回答

290

使用Guava您可以使用Lists.newArrayList(Iterable)Sets.newHashSet(Iterable)以及其他類似的方法。這當然會將所有元素複製到內存中。如果這是不可接受的,我認爲你的代碼應該採取Iterable,而不是Collection。番石榴也碰巧提供了方便的方法來處理您在Collection上使用Iterable(例如Iterables.isEmpty(Iterable)Iterables.contains(Iterable, Object))可以做的事情,但性能影響更爲明顯。

+1

是否直接遍歷所有元素? I.e.是'Lists.newArrayList(Iterable).clear()'線性或常量時間操作? – aioobe

+1

@aioobe:它創建了一個可迭代的副本。沒有規定視圖是需要的,並且考慮到'Collection'上的大多數方法或者不能用於'Iterable'視圖或者效率不高,它沒有多大意義對我來說這樣做。 – ColinD

+0

@ColinD如果我想要一個視圖怎麼辦?實際上,我想要的是一個集合視圖,它是使用另一個元素附加一個源集合的結果。我可以使用'Iterables.concat()',但給出一個'Iterable',而不是'Collection' :( –

4

只要您撥打containscontainsAllequalshashCoderemoveretainAllsizetoArray,你無論如何都要穿越元素。

如果你偶爾只調用方法,如isEmptyclear我想你會更好的創建集合的懶惰。例如,你可以有一個支持ArrayList來存儲以前迭代的元素。

我不知道任何這樣的類在任何庫中,但它應該是一個相當簡單的練習寫出來。

+1

田田.. https://github.com/soluvas/soluvas -framework/commit/34b22c3f20b9d55f061d2ef8c539efb995af15d6 :) –

16

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

它是否直接迭代所有元素?即,是'Lists.newArrayList(someIterable).clear()'線性或常量時間操作? – aioobe

+0

我添加了addAll的源代碼,顧名思義,它 –

+0

很遺憾,'CollectionUtils'中沒有任何方法可以跳過創建集合的額外行 –

72

你可能寫這個你自己的工具方法,以及:

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

+1如果從'Iterable'到'收藏'是唯一的關注ñ,我更喜歡這種方式通過導入一個大的第三方集合庫。 – aioobe

+1

在2 MB的編譯庫代碼中,4行函數代碼更爲可取,而其中99%的代碼未被使用。還有另一個成本:許可併發症。 Apache 2.0許可證是靈活的,但不是沒有一些繁瑣的任務。理想情況下,我們會看到這些常見模式中的一些直接集成到Java運行時庫中。 –

+2

還有一點,因爲您無論如何都在使用ArrayList,爲什麼不簡單地使用協變List類型呢?這使您可以在不下調或重新構建的情況下滿足更多的合同,而Java無論如何都不支持較低的類型邊界。 –

14

儘管如此,不要忘記所有集合都是有限的,而Iterable沒有任何承諾任何。如果某些東西是可迭代的,你可以得到一個迭代器,就是這樣。

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

將擴大到:

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

it.hasNext()不需要永遠返回false。因此,在一般情況下,您無法期望能夠將每個Iterable轉換爲Collection。例如,你可以迭代所有正數的自然數,迭代循環中的某些東西,一遍又一遍地產生相同的結果等。

否則:Atrey的答案相當好。

+1

有沒有人真的遇到過遍歷無限的Iterable(比如答案中給出的自然數例子),在實踐中還是真實代碼?我會認爲這樣的Iterable會在很多地方造成痛苦和災難...... :) – David

+2

@David雖然我不能專門指向任何生產代碼中的無限迭代器,但我可以想到它們的情況可能會發生。一個視頻遊戲可能有一個技巧,可以按上面的答案所建議的循環模式創建項目。 雖然我還沒有遇到任何無限迭代器,但我肯定遇到了內存真正關心的迭代器。我有迭代器在磁盤上的文件。如果我有一個完整的1TB磁盤和4GB的ram,我可以輕鬆地將內存轉換爲一個集合。 – radicaledward101

1

兩個言論

  1. 沒有必要可迭代轉換爲Collection使用的foreach 循環 - 可迭代可以直接這樣循環使用,不存在 語法差別,所以我很難理解爲什麼當初問題完全被問到。
  2. 將Iterable轉換爲Collection的建議方法是不安全的(同CollectionUtils相關) - 不保證對next()方法的後續調用返回不同的對象實例。而且,這種擔憂並不純粹是理論上的。例如。用於將值傳遞給Hadoop Reducer的reduce方法的可迭代實現總是返回相同的值實例,只是具有不同的字段值。所以如果你從上面應用makeCollection(或者CollectionUtils.addAll(Iterator)),你最終將得到一個包含所有相同元素的集合。
28

IteratorUtilscommons-collections可以幫助(雖然他們不支持3.2.1最新穩定版仿製藥):

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

4.0版(這是在快照在這一刻)支持泛型和你可以擺脫@SuppressWarnings

更新:從Cactoos檢查IterableAsList

+5

但是這需要一個迭代器,而不是一個可迭代的 – hithwen

+2

@hithwen,我不明白 - Iterable提供了一個迭代器(詳見答案) - 最新的問題是什麼? – Tom

+0

不知道我在想什麼^^ U – hithwen

6

這不是您的問題的答案,但我相信這是解決您的問題。界面org.springframework.data.repository.CrudRepository確實有方法返回java.lang.Iterable但您不應該使用此接口。相反,使用子接口,你的情況org.springframework.data.mongodb.repository.MongoRepository。該接口具有返回java.util.List類型對象的方法。

+0

我會促進使用通用的CrudRepository以避免將您的代碼綁定到具體的實現。 – stanlick

12

我使用FluentIterable.from(myIterable).toList()很多。

3

在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答案的啓發。

40

與Java 8簡潔的解決方案使用java.util.stream

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

與'commons-collections'的'IteratorUtils'相比,這種方法太慢了。 –

+0

慢多少? 'IteratorUtils.toList()'以預先Java 5的方式使用迭代器將元素逐一添加到新創建的列表中。簡單和可能最快,但增加了734 kB到你的二進制文件,如果你發現這個方法是最好的,你可以自己做。 – xehpuk

+5

我做了一個基本的基準測試,得出的結論是有時第一個更快,有時第二個更快。向我們展示你的基準。 – xehpuk

5

我用我的自定義工具(如有)投現有的集合。

主:

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

我實施的集合所有亞型(設置,列表等)類似的實用程序。我認爲這些已經是番石榴的一部分,但我還沒有找到它。

+0

你一歲的答案是一個新問題的基礎http://stackoverflow.com/questions/32570534/are-there-any-java-standard-classes-that-implement-iterable-without-implementing吸引大量的意見和評論。 –

180

在JDK 8,而不依賴於額外的庫:

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

編輯:上述一個爲Iterator。如果你正在處理Iterable

iterable.forEach(target::add); 
+31

或'iterable.forEach(target :: add);' – Cephalopod

+1

非常感謝你@Cephalopod – ozgur

3

由於RxJava是一把錘子,這還挺看起來都像釘子,你可以做

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

有沒有辦法讓jquery參與? –

+0

如果RxJava中有空項,它將崩潰。不是嗎? – MBH

+0

我相信RxJava2不允許空項目,應該在RxJava中罰款。 – DariusL

2

下面是一個偉大的方式在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); 
    } 
} 
0

Cactoos嘗試StickyList

List<String> list = new StickyList<>(iterable); 
相關問題