Einfache Möglichkeit, Iterable in Collection umzuwandeln

423

In meiner Anwendung verwende ich eine Bibliothek von Drittanbietern (Spring Data für MongoDB um genau zu sein).

Die Methoden dieser Bibliothek kehren zurück Iterable<T>, während der Rest meines Codes dies erwartetCollection<T> .

Gibt es irgendwo eine Dienstprogrammmethode, mit der ich schnell eine in die andere konvertieren kann? Ich möchte vermeiden, foreachfür eine so einfache Sache eine Reihe von Schleifen in meinem Code zu erstellen .

Ula Krukar
quelle
3
Jede nützliche Methode zum Ausführen des Vorgangs muss ohnehin die Sammlung durchlaufen, sodass Sie keinen Leistungsgewinn erwarten können. Aber wenn Sie nur nach syntaktischem Zucker suchen, würde ich mich für Guava oder vielleicht Apache Collections entscheiden.
Sebastian Ganslandt
"muss die Sammlung sowieso iterieren ", - nein, das ist es nicht. Siehe meine Antwort für Details.
Aioobe
3
In Ihrem speziellen Anwendungsfall können Sie CrudRepository einfach um Ihre eigene Schnittstelle mit Methoden erweitern, die Collection <T> / List <T> / Set <T> (nach Bedarf) anstelle von Iterable <T> zurückgeben
Kevin Van Dyck,

Antworten:

387

Mit Guava können Sie unter anderem Lists.newArrayList (Iterable) oder Sets.newHashSet (Iterable) verwenden. Dadurch werden natürlich alle Elemente in den Speicher kopiert. Wenn das nicht akzeptabel ist, sollte Ihr Code, der mit diesen funktioniert, Iterableeher als nehmen Collection. Guava bietet auch bequeme Methoden, um Dinge zu tun, die Sie Collectionmit einem Iterable(wie Iterables.isEmpty(Iterable)oder Iterables.contains(Iterable, Object)) tun können , aber die Auswirkungen auf die Leistung sind offensichtlicher.

ColinD
quelle
1
Durchläuft es alle Elemente direkt? Dh ist Lists.newArrayList(Iterable).clear()eine lineare oder konstante Zeitoperation?
Aioobe
2
@aioobe: Es wird eine Kopie des iterable erstellt. Es wurde nicht angegeben, dass eine Ansicht gewünscht wurde, und da die meisten Methoden Collectionentweder nicht für eine Ansicht von implementiert werden können Iterableoder nicht effizient sind, ist es für mich nicht sehr sinnvoll, dies zu tun.
ColinD
@ColinD was ist, wenn ich eine Ansicht haben möchte? Eigentlich möchte ich eine Sammlungsansicht, die das Ergebnis des Anhängens einer Quellensammlung an ein anderes Element ist. Ich kann verwenden, Iterables.concat()aber das gibt ein Iterable, nicht ein Collection:(
Hendy Irawan
1
Dies ist meine Frage: stackoverflow.com/questions/4896662/… . Leider ist die einfache Antwort, die das Problem nicht löst, die Verwendung von Iterables.concat(). Die viel längere Antwort gibt Collection... Ich frage mich, warum dies nicht häufiger unterstützt wird.
Hendy Irawan
365

In JDK 8+ ohne Verwendung zusätzlicher Bibliotheken:

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

Bearbeiten: Das obige ist für Iterator. Wenn Sie es zu tun haben Iterable,

iterable.forEach(target::add);
Thamme Gowda
quelle
86
Oderiterable.forEach(target::add);
Kopffüßer
92

Sie können auch hierfür Ihre eigene Dienstprogrammmethode schreiben:

public static <E> Collection<E> makeCollection(Iterable<E> iter) {
    Collection<E> list = new ArrayList<E>();
    for (E item : iter) {
        list.add(item);
    }
    return list;
}
Atreys
quelle
33
+1 Wenn der Wechsel von Iterablezu Collectiondas einzige Problem ist, würde ich diesen Ansatz dem Import einer großen Sammlungsbibliothek von Drittanbietern vorziehen.
Aioobe
2
4 Zeilen Funktionscode sind 2 MB kompiliertem Bibliothekscode vorzuziehen, für den 99% nicht verwendet werden. Es gibt noch weitere Kosten: Lizenzierungskomplikationen. Die Apache 2.0-Lizenz ist flexibel, jedoch nicht ohne langwierige Mandate. Im Idealfall werden einige dieser allgemeinen Muster direkt in die Java-Laufzeitbibliotheken integriert.
Jonathan Neufeld
2
Ein weiterer Punkt, da Sie sowieso eine ArrayList verwenden, warum nicht einfach stattdessen den kovarianten Listentyp verwenden? Auf diese Weise können Sie mehr Verträge erfüllen, ohne sie herunterzuwerfen oder neu zu komponieren, und Java unterstützt ohnehin keine unteren Typgrenzen.
Jonathan Neufeld
@ JonathanNeufeld oder warum nicht einfach eine ArrayList <T> zurückgeben?
Juan
5
@ Juan Weil das nicht sehr FEST ist . Eine ArrayList macht Implementierungsdetails verfügbar, die höchstwahrscheinlich unnötig sind (YAGNI), was gegen die Prinzipien der Inversion von Einzelverantwortung und Abhängigkeit verstößt. Ich würde es bei List belassen, weil es etwas mehr als Collection freilegt und dabei völlig FEST bleibt. Wenn Sie sich Sorgen über die Auswirkungen des Opcodes INVOKEINTERFACE auf die JVM-Leistung gegenüber INVOKEVIRTUAL machen, werden zahlreiche Benchmarks zeigen, dass es sich nicht lohnt, den Schlaf zu verlieren.
Jonathan Neufeld
80

Prägnante Lösung mit Java 8 mit java.util.stream:

public static <T> List<T> toList(final Iterable<T> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false)
                        .collect(Collectors.toList());
}
xehpuk
quelle
1
Dieser Ansatz ist viel zu langsam im Vergleich zu IteratorUtilsauscommons-collections
Alex Burduşel
3
Wie viel langsamer? IteratorUtils.toList()Verwendet den Iterator vor Java 5, um die Elemente einzeln zu einer neu erstellten Liste hinzuzufügen. Einfach und möglicherweise am schnellsten, fügt Ihrer Binärdatei jedoch 734 kB hinzu, und Sie können dies selbst tun, wenn Sie diese Methode als die beste empfinden.
Xehpuk
8
Ich habe einen primitiven Benchmark durchgeführt und festgestellt, dass manchmal der erste schneller ist, manchmal der zweite schneller. Zeigen Sie uns Ihren Benchmark.
Xehpuk
Dieses Problem könnte eine neue akzeptierte Antwort sein. Es ist schön, übermäßige Bibliotheken (wie Guave) zu vermeiden.
Java-Addict301
48

IteratorUtilsfrom commons-collectionskann helfen (obwohl sie in der neuesten stabilen Version 3.2.1 keine Generika unterstützen):

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

Version 4.0 (die derzeit in SNAPSHOT enthalten ist) unterstützt Generika, und Sie können die Version entfernen @SuppressWarnings.

Update: Überprüfen Sie IterableAsListvon Cactoos .

yegor256
quelle
5
Aber das erfordert einen Iterator, keinen Iterierbaren
bis zum
5
@hithwen, ich verstehe es nicht - Iterable bietet einen Iterator (wie in der Antwort beschrieben) - was ist das Problem?
Tom
Ich weiß nicht was ich dachte ^^ U
hithwen
2
Seit 4.1 gibt es auch IterableUtils.toList(Iterable), was eine bequeme Methode ist und IteratorUtilsunter der Haube verwendet, aber auch null-sicher ist (im Gegensatz zu IteratorUtils.toList).
Yoory N.
21

Aus CollectionUtils :

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

Hier sind die vollständigen Quellen dieser Dienstprogrammmethode:

public static <T> void addAll(Collection<T> collection, Iterator<T> iterator) {
    while (iterator.hasNext()) {
        collection.add(iterator.next());
    }
}
Tomasz Nurkiewicz
quelle
Durchläuft es alle Elemente direkt? Dh ist Lists.newArrayList(someIterable).clear()eine lineare oder konstante Zeitoperation?
Aioobe
Ich habe den Quellcode hinzugefügt, der addAll, wie der Name schon sagt, die Iteratorwerte nacheinander kopiert. Es wird eher eine Kopie als eine Ansicht erstellt.
Tomasz Nurkiewicz
Schade, dass es keine Methode gibt CollectionUtils, die Erstellung der Sammlung in einer zusätzlichen Zeile zu überspringen.
Karl Richter
Broken Link ☝️☝️
Hola Soja Edu Feliz Navidad
14

Vergessen Sie dabei nicht, dass alle Sammlungen endlich sind, während Iterable keinerlei Versprechen hat. Wenn etwas iterierbar ist, können Sie einen Iterator bekommen und das ist es.

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

wird erweitert auf:

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

it.hasNext () muss niemals false zurückgeben. Daher können Sie im allgemeinen Fall nicht erwarten, dass Sie jedes Iterable in eine Sammlung konvertieren können. Sie können beispielsweise alle positiven natürlichen Zahlen durchlaufen, etwas mit Zyklen durchlaufen, die immer wieder dieselben Ergebnisse liefern usw.

Ansonsten: Atreys Antwort ist ganz gut.

Alexander Shopov
quelle
1
Hat jemand jemals tatsächlich eine Iterable gefunden, die über etwas Unendliches iteriert (wie das Beispiel für natürliche Zahlen in der Antwort), in der Praxis / in echtem Code? Ich würde denken, dass solch ein Iterable an vielen Stellen Schmerzen und Leiden verursachen würde ... :)
David
2
@David Obwohl ich in keinem meiner Produktionscodes speziell auf einen unendlichen Iterator verweisen kann, kann ich mir Fälle vorstellen, in denen sie auftreten könnten. Ein Videospiel verfügt möglicherweise über eine Fähigkeit, mit der Elemente im zyklischen Muster erstellt werden, das in der obigen Antwort vorgeschlagen wird. Während ich keine unendlichen Iteratoren angetroffen habe, bin ich definitiv auf Iteratoren gestoßen, bei denen der Speicher ein echtes Problem darstellt. Ich habe Iteratoren über die Dateien auf der Festplatte. Wenn ich eine volle 1-TB-Festplatte und 4 GB RAM habe, kann mir leicht der Speicher ausgehen, der meinen Iterator in eine Sammlung konvertiert.
radikaledward101
14

Ich benutze FluentIterable.from(myIterable).toList()viel.

fringd
quelle
9
Zu beachten ist, dass es auch aus Guave ist.
Vadzim
Oder aus org.apache.commons.collections4. Dann ist es FluentIterable.of (myIterable) .toList ()
du-it
9

Dies ist keine Antwort auf Ihre Frage, aber ich glaube, es ist die Lösung für Ihr Problem. Die Schnittstelle org.springframework.data.repository.CrudRepositoryverfügt tatsächlich über Methoden, die zurückkehrenjava.lang.Iterable , Sie sollten diese Schnittstelle jedoch nicht verwenden. Verwenden Sie in Ihrem Fall stattdessen Subschnittstellen org.springframework.data.mongodb.repository.MongoRepository. Diese Schnittstelle verfügt über Methoden, die Objekte vom Typ zurückgeben java.util.List.

Ludwig Magnusson
quelle
2
Ich würde die Verwendung des generischen CrudRepository fördern, um zu vermeiden, dass Ihr Code an eine konkrete Implementierung gebunden wird.
Stanlick
7

Ich verwende mein benutzerdefiniertes Dienstprogramm, um eine vorhandene Sammlung zu übertragen, falls verfügbar.

Main:

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

Im Idealfall wird ImmutableList verwendet, ImmutableCollection lässt jedoch keine Nullen zu, die zu unerwünschten Ergebnissen führen können.

Tests:

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

Ich implementiere ähnliche Dienstprogramme für alle Untertypen von Sammlungen (Set, List usw.). Ich würde denken, dass diese bereits Teil von Guava sind, aber ich habe sie nicht gefunden.

Aaron Roller
quelle
1
Ihre jahrelange Antwort ist die Grundlage für eine neue Frage stackoverflow.com/questions/32570534/…, die viele Ansichten und Kommentare anzieht .
Paul Boddington
6

Sobald Sie rufen contains, containsAll, equals, hashCode, remove, retainAll, sizeoder toArraywürden Sie die Elemente ohnehin durchqueren müssen.

Wenn Sie gelegentlich nur Methoden wie isEmptyoder aufrufen, ist cleares wahrscheinlich besser, wenn Sie die Sammlung träge erstellen. Sie könnten beispielsweise einen Hintergrund ArrayListzum Speichern zuvor iterierter Elemente haben.

Ich kenne keine solche Klasse in einer Bibliothek, aber es sollte eine ziemlich einfache Übung sein, sie aufzuschreiben.

aioobe
quelle
6

In Java 8 können Sie dies tun, um alle Elemente von einem Iterablebis hinzuzufügen Collectionund zurückzugeben:

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

Inspiriert von @Afreys Antwort.


quelle
5

Da RxJava ein Hammer ist und dies irgendwie wie ein Nagel aussieht, können Sie dies tun

Observable.from(iterable).toList().toBlocking().single();
DariusL
quelle
23
Gibt es eine Möglichkeit, jquery vielleicht einzubeziehen?
Dmitry Minkovsky
3
Es stürzt ab, wenn in RxJava ein Nullelement vorhanden ist. ist es nicht?
MBH
Ich glaube, RxJava2 erlaubt keine Null-Elemente, sollte in RxJava in Ordnung sein.
DariusL
4

Hier ist eine SSCCE für eine großartige Möglichkeit, dies in Java 8 zu tun

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);
    }
}
michaelsnowden
quelle
2

Zwei Bemerkungen

  1. Es ist nicht erforderlich, Iterable in Collection zu konvertieren, um foreach-Schleife zu verwenden. Iterable kann in einer solchen Schleife direkt verwendet werden. Es gibt keinen syntaktischen Unterschied. Daher verstehe ich kaum, warum die ursprüngliche Frage überhaupt gestellt wurde.
  2. Die vorgeschlagene Methode zum Konvertieren von Iterable in Collection ist unsicher (dasselbe gilt für CollectionUtils). Es gibt keine Garantie dafür, dass nachfolgende Aufrufe der next () -Methode unterschiedliche Objektinstanzen zurückgeben. Darüber hinaus ist dieses Anliegen nicht rein theoretisch. Beispielsweise gibt eine iterierbare Implementierung, die zum Übergeben von Werten an eine Reduktionsmethode von Hadoop Reducer verwendet wird, immer dieselbe Wertinstanz zurück, nur mit unterschiedlichen Feldwerten. Wenn Sie also makeCollection von oben (oder CollectionUtils.addAll (Iterator)) anwenden, erhalten Sie eine Sammlung mit allen identischen Elementen.
al0
quelle
1

Versuchen StickyListvon Cactoos :

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

Ich habe keine einfache einzeilige Lösung ohne Abhängigkeiten gesehen. Ich benutze einfach

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

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

Ich bin auf eine ähnliche Situation gestoßen , als ich versucht habe, ein Listvon Projects abzurufen , anstatt den Iterable<T> findAll()in der CrudRepositorySchnittstelle deklarierten Standard . Also habe ich in meiner ProjectRepositorySchnittstelle (die sich von erstreckt CrudRepository) einfach die findAll()Methode deklariert , um a List<Project>anstelle von zurückzugeben 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();
}

Ich denke, dies ist die einfachste Lösung, ohne dass eine Konvertierungslogik oder die Verwendung externer Bibliotheken erforderlich ist.

Manish Giri
quelle
0

Sie können Eclipse Collections- Fabriken verwenden:

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

Sie können die auch konvertieren Iterable zu ein LazyIterableund das Konverter - Verfahren oder eine der anderen verfügbaren APIs zur Verfügung verwenden.

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

Alle oben genannten MutableTypen erstrecken sich java.util.Collection.

Hinweis: Ich bin ein Committer für Eclipse-Sammlungen.

Donald Raab
quelle