Łatwy sposób przekonwertować Iterable na Collection


424

W mojej aplikacji korzystam z biblioteki innej firmy (dokładniej Spring Data dla MongoDB).

Metody tej biblioteki zwracają się Iterable<T>, podczas gdy reszta mojego kodu oczekuje Collection<T>.

Czy jest gdzieś jakaś metoda narzędziowa, która pozwoli mi szybko przekonwertować jedną na drugą? Chciałbym uniknąć tworzenia kilkuforeach pęków w moim kodzie dla tak prostej rzeczy.


3
Każda użyteczna metoda wykonania operacji i tak jest związana z iteracją kolekcji, więc nie można oczekiwać żadnego wzrostu wydajności. Ale jeśli szukasz cukru syntaktycznego, wybrałbym Guava lub kolekcje Apache.
Sebastian Ganslandt

„i tak jest związany z iteracją kolekcji ”, - nie, nie jest. Zobacz moją odpowiedź, aby poznać szczegóły.
aioobe

3
w twoim konkretnym przypadku użycia możesz po prostu rozszerzyć CrudRepository o własny interfejs o metody zwracające Collection <T> / List <T> / Set <T> (w razie potrzeby) zamiast Iterable <T>
Kevin Van Dyck

Odpowiedzi:


387

Z Guava możesz użyć Lists.newArrayList (Iterable) lub Sets.newHashSet (Iterable) , wśród innych podobnych metod. To oczywiście skopiuje wszystkie elementy do pamięci. Jeśli to nie jest dopuszczalne, uważam, że twój kod, który z nimi współpracuje, powinien Iterableraczej zająć niż Collection. Zdarza się również, że Guava zapewnia wygodne metody robienia rzeczy, które można wykonać za Collectionpomocą Iterable(takich jak Iterables.isEmpty(Iterable)lub Iterables.contains(Iterable, Object)), ale implikacje dotyczące wydajności są bardziej oczywiste.


1
Czy iteruje bezpośrednio wszystkie elementy? Tj. Jest Lists.newArrayList(Iterable).clear()operacja liniowa czy stała?
aioobe

2
@ aioobe: Tworzy kopię iterowalnego. Nie określono, że widok jest pożądany, a biorąc pod uwagę, że większość metod Collectionalbo nie można zaimplementować dla widoku, Iterablealbo nie będzie wydajna, nie ma dla mnie sensu, aby to robić.
ColinD

@ColinD co jeśli chcę widok? Właściwie to chcę widok Kolekcji, który jest wynikiem dołączenia Kolekcji źródłowej z innym elementem. Mogę użyć, Iterables.concat()ale daje to Iterable, a nie Collection:(
Hendy Irawan

1
To jest moje pytanie: stackoverflow.com/questions/4896662/... . Niestety prosta odpowiedź, która nie rozwiązuje problemu, polega na użyciu Iterables.concat(). Dłuższa odpowiedź daje Collection... Zastanawiam się, dlaczego nie jest to częściej obsługiwane?
Hendy Irawan

365

W JDK 8+, bez korzystania z dodatkowych bibliotek:

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

Edycja: Powyższy jest dla Iterator. Jeśli masz do czynienia Iterable,

iterable.forEach(target::add);

86
Lubiterable.forEach(target::add);
Głowonogi

92

Możesz również napisać własną metodę narzędzia:

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

33
+1 Jeśli przejście od Iterabledo Collectionjest jedynym problemem, wolę takie podejście niż importowanie dużej biblioteki zbiorów zewnętrznych.
aioobe

2
4 wiersze kodu funkcji są znacznie bardziej preferowane niż 2 MB skompilowanego kodu biblioteki, dla którego 99% nie jest używane. Jest jeszcze jeden koszt: komplikacje związane z licencjonowaniem. Licencja Apache 2.0 jest elastyczna, ale nie bez żmudnych mandatów. Idealnie byłoby zobaczyć niektóre z tych powszechnych wzorców zintegrowane bezpośrednio z bibliotekami wykonawczymi Java.
Jonathan Neufeld

2
Jeszcze jeden punkt, skoro i tak używasz ArrayList, dlaczego po prostu nie użyć zamiast tego typu listy kowariantnej? Pozwala to na spełnienie większej liczby kontraktów bez konieczności rzucania w dół lub ponownej kompozycji, a Java i tak nie obsługuje dolnych granic typów.
Jonathan Neufeld

@JonathanNeufeld lub dlaczego nie po prostu zwrócić ArrayList <T>?
Juan

5
@Juan Bo to nie jest bardzo SOLID . ArrayList ujawnia szczegóły implementacji, które najprawdopodobniej są niepotrzebne (YAGNI), co narusza zasady pojedynczej odpowiedzialności i zasady inwersji zależności. Zostawiłbym to na liście, ponieważ ujawnia nieco więcej niż Collection, pozostając całkowicie SOLIDNYM. Jeśli martwisz się wpływem JVM na działanie opcode INVOKEINTERFACE w porównaniu z INVOKEVIRTUAL, wiele testów porównawczych ujawni, że nie warto przespać się.
Jonathan Neufeld,

80

Zwięzłe rozwiązanie z Javą 8 przy użyciu java.util.stream:

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

1
takie podejście jest o wiele za wolne w porównaniu IteratorUtilszcommons-collections
Alex Burdusel,

3
Ile wolniej? IteratorUtils.toList()używa iteratora w sposób wcześniejszy niż Java 5, aby dodawać elementy jeden po drugim do nowo utworzonej listy. Prosty i prawdopodobnie najszybszy, ale dodaje 734 kB do twojego pliku binarnego i możesz to zrobić sam, jeśli uważasz, że ta metoda jest najlepsza.
xehpuk

8
Zrobiłem prymitywny test porównawczy, stwierdzając, że czasami pierwszy jest szybszy, a czasem drugi jest szybszy. Pokaż nam swój punkt odniesienia.
xehpuk

ten problem może być nową przyjętą odpowiedzią - Miło jest unikać nadmiaru lib (jak Guava).
java-addict301

48

IteratorUtilsz commons-collectionsmoże pomóc (chociaż nie obsługują one generycznych w najnowszej stabilnej wersji 3.2.1):

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

Wersja 4.0 (która jest obecnie w SNAPSHOT) obsługuje generyczne i możesz się ich pozbyć @SuppressWarnings .

Aktualizacja: Sprawdź IterableAsListod Cactoos .


5
Ale to wymaga Iteratora, a nie
Iterowalności

5
@ Hithwen, nie rozumiem - Iterable zapewnia Iterator (jak wyszczególniono w odpowiedzi) - jaki jest problem?
Tom

Nie wiem, o czym myślałem ^^ U
hithwen

2
Od wersji 4.1 istnieje również IterableUtils.toList(Iterable)metoda wygodna i stosowana IteratorUtilspod maską, ale także zerowa (w przeciwieństwie do IteratorUtils.toList).
Yoory N.

21

Z kolekcjiUtils :

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

Oto pełne źródła tej metody narzędzia:

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

Czy iteruje bezpośrednio wszystkie elementy? Tj. Jest Lists.newArrayList(someIterable).clear()operacja liniowa czy stała?
aioobe

Dodałem kod źródłowy addAll, jak sama nazwa wskazuje, kopiuje wartości iteratora jedna po drugiej; tworzy kopię zamiast widoku.
Tomasz Nurkiewicz,

Jaka szkoda, że ​​nie ma metody CollectionUtilspominięcia tworzenia kolekcji w dodatkowej linii.
Karl Richter,

Broken Link ☝️☝️
Hola Soy Edu Feliz Navidad

14

Nie zapominając o tym, że wszystkie kolekcje są skończone, a Iterable nie ma żadnych obietnic. Jeśli coś jest Iterowalne, możesz dostać Iterator i to wszystko.

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

zostanie rozszerzony do:

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

it.hasNext () nie musi nigdy zwracać wartości false. Dlatego w ogólnym przypadku nie można oczekiwać, że będzie można przekonwertować każdy Iterable na kolekcję. Na przykład możesz iterować po wszystkich dodatnich liczbach naturalnych, iterować po czymś z cyklami, które dają te same wyniki w kółko itp.

W przeciwnym razie odpowiedź Atrey jest całkiem dobra.


1
Czy ktoś kiedykolwiek natknął się na iterowalny, który iteruje coś nieskończonego (jak przykład liczb naturalnych podany w odpowiedzi), w praktyce / prawdziwy kod? Sądzę, że taki Iterable spowodowałby ból i nieszczęścia w wielu miejscach ... :)
David

2
@David Chociaż nie mogę konkretnie wskazać nieskończonego iteratora w żadnym z moich kodów produkcyjnych, mogę myśleć o przypadkach, w których mogą wystąpić. Gra wideo może mieć umiejętność tworzenia przedmiotów w cyklicznym wzorze sugerowanym przez powyższą odpowiedź. Chociaż nie spotkałem żadnych nieskończonych iteratorów, zdecydowanie napotkałem iteratory, w których pamięć jest prawdziwym problemem. Mam iteratory plików na dysku. Jeśli mam pełny dysk 1 TB i 4 GB pamięci RAM, mogę łatwo zabraknąć pamięci, przekształcając mój iterator w kolekcję.
radykalny 101

14

Używam FluentIterable.from(myIterable).toList()dużo.


9
Należy zauważyć, że również pochodzi z Guawy.
Vadzim

Lub z org.apache.commons.collections4. Potem jest FluentIterable.of (myIterable) .toList ()
du-it

9

To nie jest odpowiedź na twoje pytanie, ale uważam, że jest to rozwiązanie twojego problemu. Interfejs org.springframework.data.repository.CrudRepositoryrzeczywiście ma metody, które zwracają, java.lang.Iterableale nie powinieneś używać tego interfejsu. Zamiast tego, w twoim przypadku, użyj interfejsów podrzędnych org.springframework.data.mongodb.repository.MongoRepository. Ten interfejs ma metody zwracające obiekty typu java.util.List.


2
Promowałbym użycie ogólnego CrudRepository, aby uniknąć powiązania kodu z konkretną implementacją.
stanlick,

7

Używam mojego niestandardowego narzędzia, by rzutować istniejącą kolekcję, jeśli jest dostępna.

Główny:

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

Idealnie powyższe użyłoby ImmutableList, ale ImmutableCollection nie dopuszcza wartości zerowych, które mogą dawać niepożądane wyniki.

Testy:

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

Implementuję podobne narzędzia dla wszystkich podtypów kolekcji (zestaw, lista itp.). Sądzę, że byłyby już częścią Guawy, ale nie znalazłem tego.


1
Twoja roczna odpowiedź jest podstawą nowego pytania stackoverflow.com/questions/32570534/... przyciągającego wiele opinii i komentarzy.
Paul Boddington,

6

Jak najszybciej zadzwonić contains, containsAll, equals, hashCode, remove, retainAll, sizelubtoArray , że pan musiał przemierzać elementy tak.

Jeśli od czasu do czasu wywołujesz tylko metody takie jak isEmptylub clear, przypuszczam, że lepiej byłoby, gdybyś utworzył kolekcję leniwie. Możesz na przykład mieć wsparcieArrayList do przechowywania wcześniej iterowanych elementów.

Nie znam żadnej takiej klasy w żadnej bibliotece, ale napisanie jej powinno być dość prostym ćwiczeniem.



6

W Javie 8 możesz to zrobić, aby dodać wszystkie elementy od Iterabledo Collectioni zwrócić:

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

Zainspirowany odpowiedzią @Afreys.


5

Ponieważ RxJava jest młotkiem, a ten rodzaj przypomina gwóźdź, możesz to zrobić

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

23
czy jest jakiś sposób na zaangażowanie się w jquery?
Dmitry Minkovsky,

3
ulega awarii, jeśli w RxJava jest pusty element. prawda?
MBH

Wierzę, że RxJava2 nie zezwala na zerowe elementy, powinno być dobrze w RxJava.
DariusL

4

Oto SSCCE za świetny sposób na zrobienie tego w Javie 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);
    }
}

2

Dwie uwagi

  1. Aby korzystać z pętli foreach, nie trzeba konwertować Iterable na Collection - Iterable można stosować bezpośrednio w takiej pętli, nie ma różnicy składniowej, więc prawie nie rozumiem, dlaczego zadano pierwotne pytanie.
  2. Sugerowany sposób konwersji Iterable na Collection jest niebezpieczny (to samo dotyczy CollectionUtils) - nie ma gwarancji, że kolejne wywołania metody next () zwrócą różne instancje obiektów. Co więcej, obawa ta nie jest czysto teoretyczna. Na przykład implementacja iteracyjna używana do przekazywania wartości do metody redukcji Hadoop Reducer zawsze zwraca tę samą instancję wartości, tylko z różnymi wartościami pola. Więc jeśli zastosujesz makeCollection z góry (lub CollectionUtils.addAll (Iterator)), otrzymasz kolekcję ze wszystkimi identycznymi elementami.

2

Natknąłem się w podobnej sytuacji, starając się sprowadzić Listz ProjectS, raczej niż domyślne Iterable<T> findAll()zadeklarowane w CrudRepositoryinterfejsie. Tak więc w moim ProjectRepositoryinterfejsie (który rozszerza się CrudRepository) po prostu zadeklarowałem findAll()metodę zwrócenia List<Project>zamiast 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();
}

To chyba najprostsze rozwiązanie, które nie wymaga logiki konwersji ani korzystania z zewnętrznych bibliotek.



1

Nie widziałem prostego rozwiązania jednoliniowego bez żadnych zależności. Jestem prosty w użyciu

List<Users> list;
Iterable<IterableUsers> users = getUsers();

// one line solution
list = StreamSupport.stream(users.spliterator(), true).collect(Collectors.toList());

0

Możesz korzystać z fabryk kolekcji 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);

Możesz także przekonwertować na Iterablea LazyIterablei użyć metod konwertera lub dowolnego innego dostępnego 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();

Wszystkie powyższe Mutabletypy rozszerzają się java.util.Collection.

Uwaga: jestem osobą odpowiedzialną za kolekcje Eclipse.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.