Un moyen facile de convertir Iterable en Collection

424

Dans mon application, j'utilise une bibliothèque tierce (Spring Data pour MongoDB pour être exact).

Les méthodes de cette bibliothèque reviennent Iterable<T>, tandis que le reste de mon code attend Collection<T>.

Existe-t-il une méthode utilitaire quelque part qui me permette de convertir rapidement l'une en l'autre? Je voudrais éviter de créer un tas de foreachboucles dans mon code pour une chose aussi simple.

Ula Krukar
la source
3
Toute méthode d'utilisation pour effectuer l'opération est de toute façon obligée d'itérer la collection, vous ne pouvez donc pas vous attendre à un gain de performances. Mais si vous cherchez juste du sucre syntaxique, je choisirais Guava ou peut-être Apache Collections.
Sebastian Ganslandt
" est obligé d'itérer la collection de toute façon ", - non, ce n'est pas le cas. Voir ma réponse pour plus de détails.
aioobe
3
dans votre cas d'utilisation spécifique, vous pouvez simplement étendre CrudRepository avec votre propre interface avec des méthodes qui retournent Collection <T> / List <T> / Set <T> (au besoin) au lieu d'itérables <T>
Kevin Van Dyck

Réponses:

387

Avec Guava, vous pouvez utiliser Lists.newArrayList (Iterable) ou Sets.newHashSet (Iterable) , entre autres méthodes similaires. Cela copiera bien sûr tous les éléments dans la mémoire. Si ce n'est pas acceptable, je pense que votre code qui fonctionne avec ceux-ci devrait prendre Iterableplutôt que Collection. La goyave fournit également des méthodes pratiques pour faire des choses que vous pouvez faire sur un en Collectionutilisant un Iterable(comme Iterables.isEmpty(Iterable)ou Iterables.contains(Iterable, Object)), mais les implications en termes de performances sont plus évidentes.

ColinD
la source
1
Est-ce qu'il parcourt directement tous les éléments? C'est à dire, est Lists.newArrayList(Iterable).clear()une opération à temps linéaire ou constant?
aioobe
2
@aioobe: Il crée une copie de l'itérable. Il n'a pas été spécifié qu'une vue était souhaitée, et étant donné que la plupart des méthodes sur l'une Collectionou l'autre ne peuvent pas être implémentées pour une vue d'un Iterableou ne seront pas efficaces, cela n'a pas beaucoup de sens pour moi de le faire.
ColinD
@ColinD et si je veux une vue? En fait, ce que je veux, c'est une vue Collection qui est le résultat de l'ajout d'une collection source avec un autre élément. Je peux utiliser Iterables.concat()mais cela donne un Iterable, pas un Collection:(
Hendy Irawan
1
Voici ma question: stackoverflow.com/questions/4896662/… . Malheureusement, la réponse simple qui ne résout pas le problème est d'utiliser Iterables.concat(). La réponse beaucoup plus longue donne Collection... Je me demande pourquoi ce n'est pas plus couramment pris en charge?
Hendy Irawan
365

Dans JDK 8+, sans utiliser de bibliothèques supplémentaires:

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

Edit: Celui ci-dessus est pour Iterator. Si vous avez affaire à Iterable,

iterable.forEach(target::add);
Thamme Gowda
la source
86
Ouiterable.forEach(target::add);
Céphalopode
92

Vous pouvez également écrire votre propre méthode utilitaire pour cela:

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
la source
33
+1 Si le passage de Iterableà Collectionest la seule préoccupation, je préférerais cette approche à l'importation d'une grande bibliothèque de collections tierces.
aioobe
2
4 lignes de code de fonction sont beaucoup plus préférables que 2 Mo de code de bibliothèque compilé dont 99% ne sont pas utilisés. Il y a un autre coût: les complications liées aux licences. La licence Apache 2.0 est flexible, mais pas sans quelques mandats fastidieux. Idéalement, nous verrions certains de ces modèles courants intégrés directement dans les bibliothèques d'exécution Java.
Jonathan Neufeld
2
Un point de plus, puisque vous utilisez une ArrayList de toute façon, pourquoi ne pas simplement utiliser le type de liste covariant à la place? Cela vous permet de satisfaire plus de contrats sans rétrograder ou recomposer et Java n'a de toute façon aucun support pour les limites de type inférieures.
Jonathan Neufeld
@JonathanNeufeld ou pourquoi ne pas simplement aller de l'avant et retourner un ArrayList <T>?
Juan
5
@Juan Parce que ce n'est pas très SOLIDE . Une ArrayList expose les détails d'implémentation qui sont probablement inutiles (YAGNI), ce qui viole les principes d'inversion de responsabilité et de dépendance unique. Je le laisserais à List car il expose un peu plus que Collection tout en restant complètement SOLIDE. Si vous vous inquiétez de l'impact sur les performances JVM de l'opcode INVOKEINTERFACE sur INVOKEVIRTUAL, de nombreux tests de performance révéleront qu'il ne vaut pas la peine de perdre le sommeil.
Jonathan Neufeld
81

Solution concise avec Java 8 utilisant java.util.stream:

public static <T> List<T> toList(final Iterable<T> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false)
                        .collect(Collectors.toList());
}
xehpuk
la source
1
cette approche est trop lent par rapport à IteratorUtilsdecommons-collections
Alex Burdusel
3
Combien plus lent? IteratorUtils.toList()utilise l'itérateur avant Java 5 pour ajouter les éléments un par un à une liste nouvellement créée. Simple et peut-être plus rapide, mais ajoute 734 Ko à votre binaire et vous pouvez le faire vous-même si vous trouvez que cette méthode est la meilleure.
xehpuk
8
J'ai fait un benchmark primitif concluant que parfois le premier est plus rapide, parfois le second est plus rapide. Montrez-nous votre référence.
xehpuk
ce problème pourrait être une nouvelle réponse acceptée - Il est agréable d'éviter les bibliothèques en excès (comme Guava).
java-addict301
48

IteratorUtilsfrom commons-collectionspeut aider (bien qu'ils ne prennent pas en charge les génériques dans la dernière version stable 3.2.1):

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

La version 4.0 (qui est actuellement dans SNAPSHOT) prend en charge les génériques et vous pouvez vous en débarrasser @SuppressWarnings.

Mise à jour: Vérifiez IterableAsListauprès de Cactoos .

yegor256
la source
5
Mais cela nécessite un Iterator, pas un Iterable
hithwen
5
@hithwen, je ne comprends pas - Iterable fournit un itérateur (comme détaillé dans la réponse) - quel est le problème?
Tom
Je ne sais pas à quoi je pensais ^^ U
hithwen
2
Depuis la version 4.1, il y en a aussi IterableUtils.toList(Iterable), qui est une méthode pratique et utilise IteratorUtilssous le capot, mais est également sans danger (contrairement IteratorUtils.toList).
Yoory N.
21

De CollectionUtils :

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

Voici les sources complètes de cette méthode utilitaire:

public static <T> void addAll(Collection<T> collection, Iterator<T> iterator) {
    while (iterator.hasNext()) {
        collection.add(iterator.next());
    }
}
Tomasz Nurkiewicz
la source
Est-ce qu'il parcourt directement tous les éléments? C'est à dire, est Lists.newArrayList(someIterable).clear()une opération à temps linéaire ou constant?
aioobe
J'ai ajouté le code source de addAll, comme son nom l'indique, il copie les valeurs de l'itérateur les unes après les autres; il crée une copie plutôt qu'une vue.
Tomasz Nurkiewicz
Quel dommage qu'il n'y ait pas de méthode CollectionUtilspour ignorer la création de la collection dans une ligne supplémentaire.
Karl Richter
Lien brisé ☝️☝️
Hola Soy Edu Feliz Navidad
14

Pendant que vous y êtes, n'oubliez pas que toutes les collections sont finies, alors que Iterable n'a aucune promesse. Si quelque chose est itérable, vous pouvez obtenir un itérateur et c'est tout.

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

sera étendu à:

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

it.hasNext () n'est pas obligé de renvoyer false. Ainsi, dans le cas général, vous ne pouvez pas vous attendre à pouvoir convertir chaque Iterable en une collection. Par exemple, vous pouvez itérer sur tous les nombres naturels positifs, itérer sur quelque chose avec des cycles qui produisent les mêmes résultats encore et encore, etc.

Sinon: la réponse d'Atrey est assez bonne.

Alexander Shopov
la source
1
Quelqu'un a-t-il déjà rencontré un Iterable qui itère sur quelque chose d'infini (comme l'exemple des nombres naturels donné dans la réponse), dans la pratique / le code réel? Je penserais qu'un tel Iterable causerait des douleurs et des malheurs dans beaucoup d'endroits ... :)
David
2
@David Bien que je ne puisse pas pointer spécifiquement vers un Iterator infini dans aucun de mes codes de production, je peux penser à des cas où ils pourraient se produire. Un jeu vidéo peut avoir une compétence qui crée des éléments dans le modèle cyclique que la réponse ci-dessus suggère. Bien que je n'ai pas rencontré d'itérateurs infinis, j'ai certainement rencontré des itérateurs où la mémoire est une préoccupation réelle. J'ai des itérateurs sur les fichiers sur le disque. Si j'ai un disque de 1 To complet et 4 Go de RAM, je pourrais facilement manquer de mémoire en convertissant mon itérateur en collection.
radicaledward101
14

J'utilise FluentIterable.from(myIterable).toList()beaucoup.

fringd
la source
9
Il faut noter que c'est aussi de la goyave.
Vadzim
Ou depuis org.apache.commons.collections4. Ensuite, c'est FluentIterable.of (myIterable) .toList ()
du-it
9

Ce n'est pas une réponse à votre question mais je crois que c'est la solution à votre problème. L'interface a en org.springframework.data.repository.CrudRepositoryeffet des méthodes qui retournent java.lang.Iterablemais vous ne devez pas utiliser cette interface. Utilisez plutôt des sous-interfaces, dans votre cas org.springframework.data.mongodb.repository.MongoRepository. Cette interface possède des méthodes qui renvoient des objets de type java.util.List.

Ludwig Magnusson
la source
2
Je recommanderais d'utiliser le générique CrudRepository pour éviter de lier votre code à une implémentation concrète.
stanlick
7

J'utilise mon utilitaire personnalisé pour caster une collection existante si disponible.

Principale:

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

Idéalement, ce qui précède utiliserait ImmutableList, mais ImmutableCollection ne permet pas les valeurs nulles qui peuvent fournir des résultats indésirables.

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

J'implémente des utilitaires similaires pour tous les sous-types de collections (Set, List, etc.). Je pense que ceux-ci feraient déjà partie de la goyave, mais je ne l'ai pas trouvé.

Aaron Roller
la source
1
Votre réponse vieille d'un an est la base d'une nouvelle question stackoverflow.com/questions/32570534/… attirant beaucoup de vues et de commentaires.
Paul Boddington du
6

Dès que vous appelez contains, containsAll, equals, hashCode, remove, retainAll, sizeou toArray, vous auriez à traverser les éléments de toute façon.

Si vous n'appelez occasionnellement que des méthodes telles que isEmptyou clearje suppose que vous feriez mieux de créer la collection paresseusement. Vous pouvez par exemple avoir un support ArrayListpour stocker des éléments précédemment itérés.

Je ne connais aucune de ces classes dans aucune bibliothèque, mais ce devrait être un exercice assez simple à rédiger.

aioobe
la source
6

En Java 8 , vous pouvez le faire pour ajouter tous les éléments d'un Iterableà Collectionet le retourner:

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

Inspiré par la réponse @Afreys.


la source
5

Depuis RxJava est un marteau et cela ressemble un peu à un clou, vous pouvez le faire

Observable.from(iterable).toList().toBlocking().single();
DariusL
la source
23
existe-t-il un moyen d'impliquer jquery peut-être?
Dmitry Minkovsky
3
il se bloque s'il y a un élément nul dans RxJava. n'est-ce pas?
MBH
Je crois que RxJava2 n'autorise pas les éléments nuls, devrait être bien dans RxJava.
DariusL
4

Voici un SSCCE pour un excellent moyen de le faire dans 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);
    }
}
michaelsnowden
la source
4

Je suis tombé sur une situation similaire en essayant de récupérer un Listde Projects, plutôt que la valeur par défaut Iterable<T> findAll()déclarée dans l' CrudRepositoryinterface. Donc, dans mon ProjectRepositoryinterface (qui s'étend de CrudRepository), j'ai simplement déclaré la findAll()méthode pour retourner un List<Project>au lieu de 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();
}

C'est la solution la plus simple, je pense, sans nécessiter de logique de conversion ni d'utilisation de bibliothèques externes.

Manish Giri
la source
2

Deux remarques

  1. Il n'est pas nécessaire de convertir Iterable en Collection pour utiliser chaque boucle - Iterable peut être utilisé directement dans une telle boucle, il n'y a pas de différence syntaxique, donc je comprends à peine pourquoi la question d'origine a été posée.
  2. La méthode suggérée pour convertir Iterable en Collection n'est pas sûre (il en va de même pour CollectionUtils) - il n'y a aucune garantie que les appels suivants à la méthode next () renvoient différentes instances d'objet. De plus, cette préoccupation n'est pas purement théorique. Par exemple, l'implémentation itérative utilisée pour transmettre des valeurs à une méthode de réduction de Hadoop Reducer renvoie toujours la même instance de valeur, uniquement avec des valeurs de champ différentes. Donc, si vous appliquez makeCollection d'en haut (ou CollectionUtils.addAll (Iterator)), vous vous retrouverez avec une collection avec tous les éléments identiques.
al0
la source
1

Essayez StickyListavec Cactoos :

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

Je n'ai pas vu de solution simple sur une seule ligne sans aucune dépendance. J'utilise simplement

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

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

Vous pouvez utiliser les usines Eclipse Collections :

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

Vous pouvez également convertir le fichier Iterableen un LazyIterableet utiliser les méthodes de conversion ou l'une des autres API disponibles disponibles.

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

Tous les Mutabletypes ci-dessus s'étendent java.util.Collection.

Remarque: je suis un committer pour les collections Eclipse.

Donald Raab
la source