Manera fácil de convertir Iterable a Collection

424

En mi aplicación utilizo una biblioteca de terceros (Spring Data para MongoDB para ser exactos).

Los métodos de esta biblioteca regresan Iterable<T>, mientras que el resto de mi código espera Collection<T>.

¿Hay algún método de utilidad en algún lugar que me permita convertir rápidamente uno a otro? Me gustaría evitar crear un montón de foreachbucles en mi código para algo tan simple.

Ula Krukar
fuente
3
Cualquier método útil para realizar la operación está obligado a iterar de la colección de todos modos, por lo que no puede esperar ninguna ganancia de rendimiento. Pero si solo estás buscando azúcar sintáctica, iría por Guava o quizás por Apache Collections.
Sebastian Ganslandt
" está obligado a iterar de la colección de todos modos ", - no, no lo es. Vea mi respuesta para más detalles.
aioobe
3
en su caso de uso específico, puede extender CrudRepository con su propia interfaz con métodos que devuelven Collection <T> / List <T> / Set <T> (según sea necesario) en lugar de Iterable <T>
Kevin Van Dyck

Respuestas:

387

Con Guava puede usar Lists.newArrayList (Iterable) o Sets.newHashSet (Iterable) , entre otros métodos similares. Por supuesto, esto copiará todos los elementos en la memoria. Si eso no es aceptable, creo que su código que funciona con estos debería tomar en Iterablelugar de Collection. La guayaba también proporciona métodos convenientes para hacer cosas que puede hacer Collectionusando un Iterable(como Iterables.isEmpty(Iterable)o Iterables.contains(Iterable, Object)), pero las implicaciones de rendimiento son más obvias.

ColinD
fuente
1
¿Se itera a través de todos los elementos directamente? Es decir, ¿es Lists.newArrayList(Iterable).clear()una operación de tiempo lineal o constante?
aioobe
2
@aioobe: crea una copia del iterable. No se especificó que se deseara una vista, y dado que la mayoría de los métodos Collectionno se pueden implementar para una vista Iterableo no serán eficientes, no tiene mucho sentido hacerlo.
ColinD
@ColinD, ¿y si quiero una vista? En realidad, lo que quiero es una vista de Colección que sea el resultado de agregar una Colección de origen con otro elemento. Puedo usar Iterables.concat()pero eso da un Iterable, no un Collection:(
Hendy Irawan
1
Esta es mi pregunta: stackoverflow.com/questions/4896662/… . Lamentablemente, la respuesta simple que no resuelve el problema es mediante el uso Iterables.concat(). La respuesta mucho más larga da Collection... Me pregunto por qué esto no es más comúnmente compatible.
Hendy Irawan
365

En JDK 8+, sin usar ninguna biblioteca adicional:

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

Editar: El anterior es para Iterator. Si se trata de Iterable,

iterable.forEach(target::add);
Thamme Gowda
fuente
86
Oiterable.forEach(target::add);
Cefalópodo
92

También puede escribir su propio método de utilidad para esto:

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
fuente
33
+1 Si ir de Iterablea Collectiones la única preocupación, preferiría este enfoque a importar una gran biblioteca de colecciones de terceros.
aioobe
2
4 líneas de código de función son mucho más preferibles que 2 MB de código de biblioteca compilado para el cual el 99% no se utiliza. Hay otro costo: complicaciones de la licencia. La licencia de Apache 2.0 es flexible, pero no sin algunos mandatos tediosos. Idealmente, veríamos algunos de estos patrones comunes integrados directamente en las bibliotecas de tiempo de ejecución de Java.
Jonathan Neufeld el
2
Un punto más, ya que de todos modos está usando una ArrayList, ¿por qué no simplemente ir con el tipo de Lista covariante? Esto le permite satisfacer más contratos sin desestimar o recomponer y Java no tiene soporte para límites de tipo más bajos de todos modos.
Jonathan Neufeld el
@JonathanNeufeld o ¿por qué no seguir adelante y devolver una ArrayList <T>?
Juan
55
@Juan Porque eso no es muy SÓLIDO . Una ArrayList expone detalles de implementación que probablemente sean innecesarios (YAGNI), lo que viola los principios de responsabilidad única e inversión de dependencia. Lo dejaría en la lista porque expone un poco más de lo que hace Collection mientras permanece completamente SÓLIDO. Si le preocupa el impacto en el rendimiento JVM del código de operación INVOKEINTERFACE sobre INVOKEVIRTUAL, muchos puntos de referencia revelarán que no vale la pena perder el sueño.
Jonathan Neufeld
81

Solución concisa con Java 8 usando java.util.stream:

public static <T> List<T> toList(final Iterable<T> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false)
                        .collect(Collectors.toList());
}
xehpuk
fuente
1
este enfoque es demasiado lento en comparación con el IteratorUtilsdecommons-collections
Alex Burduşel
3
¿Cuánto más lento? IteratorUtils.toList()usa el iterador de una manera previa a Java 5 para agregar los elementos uno por uno a una lista recién creada. Simple y posiblemente más rápido, pero agrega 734 kB a su binario y podría hacerlo por su cuenta si considera que este método es el mejor.
xehpuk
8
He hecho un punto de referencia primitivo concluyendo que a veces el primero es más rápido, a veces el segundo es más rápido. Muéstranos tu punto de referencia.
xehpuk
este problema podría ser una nueva respuesta aceptada: es bueno evitar el exceso de libs (como Guava).
java-addict301
48

IteratorUtilsde commons-collectionsayudar a mayo (a pesar de que no son compatibles con los genéricos en la última versión estable 3.2.1):

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

La versión 4.0 (que está en SNAPSHOT en este momento) admite genéricos y puede deshacerse de ellos @SuppressWarnings.

Actualización: Verificación IterableAsListde Cactoos .

yegor256
fuente
55
Pero eso requiere un Iterador, no un Iterable
2015
55
@hithwen, no lo entiendo - Iterable proporciona un iterador (como se detalla en la respuesta) - ¿cuál es el problema?
Tom
No sé lo que estaba pensando ^^ U
hithwen
2
Desde 4.1 también existe IterableUtils.toList(Iterable), que es un método conveniente y se utiliza IteratorUtilsdebajo del capó, pero también es nulo seguro (a diferencia IteratorUtils.toList).
Yoory N.
21

De CollectionUtils :

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

Aquí están las fuentes completas de este método de utilidad:

public static <T> void addAll(Collection<T> collection, Iterator<T> iterator) {
    while (iterator.hasNext()) {
        collection.add(iterator.next());
    }
}
Tomasz Nurkiewicz
fuente
¿Se itera a través de todos los elementos directamente? Es decir, ¿es Lists.newArrayList(someIterable).clear()una operación de tiempo lineal o constante?
aioobe
Agregué el código fuente de addAll, como su nombre lo indica, copia los valores del iterador uno tras otro; crea una copia en lugar de una vista.
Tomasz Nurkiewicz
Qué pena que no haya ningún método CollectionUtilspara omitir la creación de la colección en una línea adicional.
Karl Richter
Broken Link ☝️☝️
Hola Soy Edu Feliz Navidad
14

Mientras lo hace, no olvide que todas las colecciones son finitas, mientras que Iterable no tiene promesas de ningún tipo. Si algo es Iterable, puede obtener un Iterator y eso es todo.

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

se ampliará a:

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

it.hasNext () no está obligado a devolver false. Por lo tanto, en el caso general, no puede esperar poder convertir cada Iterable en una Colección. Por ejemplo, puede iterar sobre todos los números naturales positivos, iterar sobre algo con ciclos que produzca los mismos resultados una y otra vez, etc.

De lo contrario: la respuesta de Atrey está bastante bien.

Alexander Shopov
fuente
1
¿Alguien ha encontrado alguna vez un Iterable que itera sobre algo infinito (como el ejemplo de números naturales que se da en la respuesta), en la práctica / código real? Creo que un Iterable como ese causaría dolor y problemas en muchos lugares ... :)
David
2
@David Aunque no puedo señalar específicamente un iterador infinito en ninguno de mis códigos de producción, puedo pensar en casos en los que podrían ocurrir. Un videojuego puede tener una habilidad que crea elementos en el patrón cíclico que sugiere la respuesta anterior. Si bien no he encontrado ningún iterador infinito, definitivamente he encontrado iteradores donde la memoria es una preocupación real. Tengo iteradores sobre los archivos en el disco. Si tengo un disco completo de 1TB y 4GB de RAM, podría quedarme sin memoria convirtiendo mi iterador en una Colección.
radicaledward101
14

Yo uso FluentIterable.from(myIterable).toList()mucho

fringd
fuente
99
Cabe señalar que también es de guayaba.
Vadzim
O de org.apache.commons.collections4. Entonces es FluentIterable.of (myIterable) .toList ()
du-it
9

Esta no es una respuesta a su pregunta, pero creo que es la solución a su problema. La interfaz org.springframework.data.repository.CrudRepositorytiene métodos que regresan, java.lang.Iterablepero no debe usar esta interfaz. En su lugar, use sub interfaces, en su caso org.springframework.data.mongodb.repository.MongoRepository. Esta interfaz tiene métodos que devuelven objetos de tipo java.util.List.

Ludwig Magnusson
fuente
2
Promovería el uso del CrudRepository genérico para evitar vincular su código a una implementación concreta.
stanlick
7

Uso mi utilidad personalizada para emitir una Colección existente si está disponible.

Principal:

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

Idealmente, lo anterior usaría ImmutableList, pero ImmutableCollection no permite valores nulos que pueden proporcionar resultados no deseados.

Pruebas:

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

Implemento utilidades similares para todos los subtipos de Colecciones (Conjunto, Lista, etc.). Creo que estos ya serían parte de Guava, pero no lo he encontrado.

Aaron Roller
fuente
1
Su respuesta de un año es la base de una nueva pregunta stackoverflow.com/questions/32570534/… que atrae muchas opiniones y comentarios.
Paul Boddington
6

Tan pronto como usted lo llama contains, containsAll, equals, hashCode, remove, retainAll, sizeo toArray, tendría que atravesar los elementos de todos modos.

Si ocasionalmente solo llama a métodos como isEmptyo clearsupongo que sería mejor crear la colección de manera perezosa. Por ejemplo, podría tener un respaldo ArrayListpara almacenar elementos iterados previamente.

No conozco ninguna clase de este tipo en ninguna biblioteca, pero debería ser un ejercicio bastante simple de escribir.

aioobe
fuente
6

En Java 8 puede hacer esto para agregar todos los elementos de un Iterableto Collectiony devolverlo:

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

Inspirado por la respuesta de @Afreys.


fuente
5

Como RxJava es un martillo y parece un clavo, puedes hacer

Observable.from(iterable).toList().toBlocking().single();
DariusL
fuente
23
¿hay alguna manera de involucrar a jquery tal vez?
Dmitry Minkovsky
3
se bloquea si hay un elemento nulo en RxJava. ¿no es así?
MBH
Creo que RxJava2 no permite elementos nulos, debería estar bien en RxJava.
DariusL
4

Aquí hay un SSCCE para una excelente manera de hacer esto en 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
fuente
4

Encontré una situación similar al intentar obtener una Listde Projects, en lugar del valor predeterminado Iterable<T> findAll()declarado en la CrudRepositoryinterfaz. Entonces, en mi ProjectRepositoryinterfaz (que se extiende desde CrudRepository), simplemente declaró que el findAll()método devolvería un en List<Project>lugar 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();
}

Esta es la solución más simple, creo, sin requerir la lógica de conversión o el uso de bibliotecas externas.

Manish Giri
fuente
2

Dos observaciones

  1. No es necesario convertir Iterable en Collection para usar el bucle foreach: Iterable se puede usar en dicho bucle directamente, no hay diferencia sintáctica, por lo que apenas entiendo por qué se hizo la pregunta original.
  2. La forma sugerida de convertir Iterable a Collection no es segura (lo mismo se refiere a CollectionUtils): no hay garantía de que las llamadas posteriores al método next () devuelvan diferentes instancias de objeto. Además, esta preocupación no es puramente teórica. Por ejemplo, la implementación Iterable utilizada para pasar valores a un método de reducción de Hadoop Reducer siempre devuelve la misma instancia de valor, solo con valores de campo diferentes. Entonces, si aplica makeCollection desde arriba (o CollectionUtils.addAll (Iterator)) terminará con una colección con todos los elementos idénticos.
al0
fuente
1

Probar StickyListde Cactoos :

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

No vi una solución simple de una línea sin ninguna dependencia. Yo uso simple

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

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

Puede usar las fábricas de 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);

También puede convertir Iterablea ay LazyIterableusar los métodos de conversión o cualquiera de las otras 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();

Todos los Mutabletipos anteriores se extienden java.util.Collection.

Nota: Soy un committer para Eclipse Collections.

Donald Raab
fuente