Maneira fácil de converter Iterável em Coleção


424

No meu aplicativo, uso a biblioteca de terceiros (Spring Data for MongoDB para ser exato).

Os métodos desta biblioteca retornam Iterable<T>, enquanto o restante do meu código esperaCollection<T> .

Existe algum método utilitário em algum lugar que me permita converter rapidamente um para o outro? Gostaria de evitar a criação de um monte de foreachloops no meu código para uma coisa tão simples.


3
Qualquer método utilitário para executar a operação está vinculado à iteração da coleção de qualquer maneira, portanto, você não pode esperar nenhum ganho de desempenho. Mas se você está apenas procurando por açúcar sintático, eu usaria a Goiaba ou talvez o Apache Collections.
Sebastian Ganslandt

" está vinculado à iteração da coleção de qualquer maneira ", - não, não é. Veja minha resposta para detalhes.
aioobe

3
em sua usecase específico, você pode simplesmente estender CrudRepository com sua própria interface com métodos que retornam Collection <T> / List <T> / Set <T> (quando necessário) em vez de Iterable <T>
Kevin Van Dyck

Respostas:


387

Com o Guava, você pode usar Lists.newArrayList (Iterable) ou Sets.newHashSet (Iterable) , entre outros métodos semelhantes. Obviamente, isso copiará todos os elementos na memória. Se isso não é aceitável, acho que seu código que funciona com estes deveria levar Iterableem vez de Collection. A goiaba também fornece métodos convenientes para fazer as coisas que você pode fazer Collectionusando um Iterable(como Iterables.isEmpty(Iterable)ou Iterables.contains(Iterable, Object)), mas as implicações de desempenho são mais óbvias.


1
Ele itera através de todos os elementos diretamente? Lists.newArrayList(Iterable).clear()Ou seja, é uma operação de tempo linear ou constante?
aioobe

2
@aioobe: Cria uma cópia do iterável. Não foi especificado que uma visualização era desejada e, como a maioria dos métodos Collectionnão pode ser implementada para uma visualização de Iterableou não será eficiente, não faz muito sentido fazer isso.
colind

@ColinD e se eu quiser uma vista? Na verdade, o que eu quero é uma exibição de coleção que é o resultado de anexar uma coleção de origem a outro elemento. Eu posso usar Iterables.concat(), mas que dá uma Iterable, não um Collection:(
Hendy Irawan

1
Esta é a minha pergunta: stackoverflow.com/questions/4896662/… . Infelizmente, a resposta simples que não resolve o problema é usando Iterables.concat(). A resposta muito mais longa dá Collection... Gostaria de saber por que isso não é mais comumente suportado?
Hendy Irawan

365

No JDK 8+, sem usar nenhuma biblioteca adicional:

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

Edit: O acima é para Iterator. Se você está lidando com Iterable,

iterable.forEach(target::add);

86
Ouiterable.forEach(target::add);
Cefalópode

92

Você também pode escrever seu próprio método utilitário:

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 Se passar de Iterablepara Collectioné a única preocupação, prefiro essa abordagem do que importar uma grande biblioteca de coleções de terceiros.
aioobe

2
4 linhas de código de função são muito mais preferíveis a 2 MB de código de biblioteca compilado, dos quais 99% não são utilizados. Há outro custo: complicações de licenciamento. A licença do Apache 2.0 é flexível, mas não sem alguns mandatos tediosos. Idealmente, veríamos alguns desses padrões comuns integrados diretamente nas bibliotecas de tempo de execução Java.
Jonathan Neufeld

2
Mais um ponto, já que você está usando um ArrayList de qualquer maneira, por que simplesmente não usar o tipo de lista covariante? Isso permite que você cumpra mais contratos sem rebaixamento ou recomposição e o Java não tem suporte para limites de tipo mais baixos.
Jonathan Neufeld

@ JonathanNeufeld ou por que não seguir em frente e retornar um ArrayList <T>?
6263 Juan

5
@ Juan Porque isso não é muito sólido . Um ArrayList expõe detalhes de implementação que provavelmente são desnecessários (YAGNI), o que viola os princípios de inversão de responsabilidade e dependência única. Gostaria de deixá-lo na lista, porque expõe um pouco mais do que a coleção, mantendo-se completamente sólido. Se você está preocupado com o impacto no desempenho da JVM do opcode INVOKEINTERFACE sobre INVOKEVIRTUAL, muitos benchmarks revelarão que não vale a pena perder o sono.
Jonathan Neufeld

80

Solução concisa com 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());
}

1
esta abordagem é muito lenta em comparação com a IteratorUtilspartir decommons-collections
Alex Burdusel

3
Quanto mais lento? IteratorUtils.toList()usa o iterador de uma maneira pré Java 5 para adicionar os elementos um por um a uma lista recém-criada. Simples e possivelmente mais rápido, mas adiciona 734 kB ao seu binário e você pode fazer isso sozinho se achar esse método o melhor.
precisa saber é o seguinte

8
Fiz uma referência primitiva concluindo que às vezes o primeiro é mais rápido, às vezes o segundo é mais rápido. Mostre-nos a sua referência.
precisa saber é

esse prob pode ser uma nova resposta aceita - É bom evitar excesso de bibliotecas (como o Goiaba).
java-addict301

48

IteratorUtilsfrom commons-collectionspode ajudar (embora eles não suportem genéricos na versão estável mais recente 3.2.1):

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

A versão 4.0 (que está no SNAPSHOT no momento) suporta genéricos e você pode se livrar do @SuppressWarnings.

Atualização: Verifique IterableAsListno Cactoos .


5
Mas isso requer um Iterator, não um Iterable
hithwen

5
@hithwen, eu não entendi - o Iterable fornece um iterador (conforme detalhado na resposta) - qual é o problema?
Tom

Não sei o que eu estava pensando ^^ U
hithwen

2
Desde o 4.1, existe também IterableUtils.toList(Iterable), que é um método de conveniência e usa IteratorUtilssob o capô, mas também é seguro para nulos (ao contrário IteratorUtils.toList).
precisa saber é o seguinte

21

Da coleçãoUtils :

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

Aqui estão as fontes completas deste método utilitário:

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

Ele itera através de todos os elementos diretamente? Lists.newArrayList(someIterable).clear()Ou seja, é uma operação de tempo linear ou constante?
aioobe

Eu adicionei o código-fonte de addAll, como o nome indica, ele copia os valores do iterador um após o outro; ele cria uma cópia em vez de uma exibição.
Tomasz Nurkiewicz

Pena que não haja método CollectionUtilspara pular a criação da coleção em uma linha extra.
Karl Richter

Link quebrado ☝️☝️
Hola Soy Edu Feliz Navidad

14

Enquanto isso, não esqueça que todas as coleções são finitas, enquanto o Iterable não tem nenhuma promessa. Se algo é iterável, você pode obter um iterador e é isso.

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

será expandido para:

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

Não é necessário que it.hasNext () retorne sempre false. Portanto, no caso geral, você não pode esperar poder converter todos os iteráveis ​​em uma coleção. Por exemplo, você pode repetir todos os números naturais positivos, repetir algo com ciclos que produzem os mesmos resultados repetidamente, etc.

Caso contrário: a resposta de Atrey é bastante boa.


1
Alguém já encontrou um Iterable que itera sobre algo infinito (como o exemplo de números naturais dado na resposta), na prática / código real? Gostaria de pensar que um Iterable tal causaria dor e aflições em um monte de lugares ... :)
David

2
@ David Embora eu não possa apontar especificamente para um iterador infinito em nenhum código de produção, posso pensar nos casos em que eles podem ocorrer. Um videogame pode ter uma habilidade que cria itens no padrão cíclico sugerido pela resposta acima. Embora não tenha encontrado iteradores infinitos, definitivamente encontrei iteradores em que a memória é uma preocupação real. Eu tenho iteradores sobre os arquivos no disco. Se eu tiver um disco completo de 1 TB e 4 GB de RAM, eu poderia ficar sem memória facilmente convertendo meu iterador em uma coleção.
precisa

14

Eu uso FluentIterable.from(myIterable).toList()muito.


9
Note-se que é de goiaba também.
Vadzim 28/06

Ou em org.apache.commons.collections4. Então é FluentIterable.of (myIterable) .toList ()
du-it

9

Esta não é uma resposta para sua pergunta, mas acredito que seja a solução para o seu problema. A interface org.springframework.data.repository.CrudRepositoryrealmente possui métodos que retornam, java.lang.Iterablemas você não deve usar essa interface. Em vez disso, use sub interfaces, no seu caso org.springframework.data.mongodb.repository.MongoRepository. Essa interface possui métodos que retornam objetos do tipo java.util.List.


2
Eu promoveria o uso do CrudRepository genérico para evitar vincular seu código a uma implementação concreta.
stanlick

7

Eu uso meu utilitário personalizado para transmitir uma coleção existente, se disponível.

A Principal:

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

Idealmente, o descrito acima usaria ImmutableList, mas ImmutableCollection não permite nulos que podem fornecer resultados indesejáveis.

Testes:

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

Eu implemento utilitários semelhantes para todos os subtipos de coleções (conjunto, lista, etc.). Eu acho que isso já faria parte da goiaba, mas não a encontrei.


1
Sua resposta de um ano é a base de uma nova pergunta stackoverflow.com/questions/32570534/… atraindo muitas visualizações e comentários.
Paul Boddington

6

Assim que você chama contains, containsAll, equals, hashCode, remove, retainAll, sizeoutoArray , você teria que atravessar os elementos de qualquer maneira.

Se você estiver ocasionalmente chamando métodos como isEmptyou cleareu suponho que você seria melhor criando a coleção preguiçosamente. Você poderia, por exemplo, ter um apoioArrayList para armazenar elementos iterados anteriormente.

Não conheço essa classe em nenhuma biblioteca, mas deve ser um exercício bastante simples de escrever.



6

No Java 8, você pode fazer isso para adicionar todos os elementos de um Iterablepara Collectione retorná-lo:

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

Inspirado pela resposta @Afreys.


5

Como o RxJava é um martelo e isso parece um prego, você pode fazer

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

23
existe alguma maneira de envolver o jquery, talvez?
Dmitry Minkovsky

3
ele falha se houver um item nulo no RxJava. não é?
MBH

Eu acredito que RxJava2 não permite itens nulos, deve ficar bem em RxJava.
precisa saber é o seguinte

4

Aqui está um SSCCE para uma ótima maneira de fazer isso no 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);
    }
}

4

Me deparei com uma situação semelhante ao tentar buscar um Listde Projects, em vez do padrão Iterable<T> findAll()declarado na CrudRepositoryinterface. Então, na minha ProjectRepositoryinterface (que se estende de CrudRepository), simplesmente declarei o findAll()método para retornar um em List<Project>vez 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 é a solução mais simples, eu acho, sem exigir lógica de conversão ou uso de bibliotecas externas.


2

Duas observações

  1. Não há necessidade de converter Iterable em Collection para usar o loop foreach - o Iterable pode ser usado diretamente nesse loop, não há diferença sintática, portanto, mal entendo por que a pergunta original foi feita.
  2. A maneira sugerida de converter Iterável em Coleção não é segura (o mesmo se aplica a CollectionUtils) - não há garantia de que as chamadas subseqüentes ao método next () retornem instâncias de objeto diferentes. Além disso, essa preocupação não é pura teórica. Por exemplo, a implementação iterável usada para passar valores para um método de redução do Hadoop Reducer sempre retorna a mesma instância de valor, apenas com valores de campo diferentes. Portanto, se você aplicar makeCollection a partir de cima (ou CollectionUtils.addAll (Iterator)), você terminará com uma coleção com todos os elementos idênticos.


1

Não vi uma solução simples de uma linha sem nenhuma dependência. Eu uso simples

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

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

0

Você pode usar as fábricas do 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);

Você também pode converter Iterablepara para LazyIterablee usar os métodos de conversão ou qualquer uma das outras APIs disponíveis.

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 os Mutabletipos acima se estendem java.util.Collection.

Nota: Sou um colaborador das Coleções Eclipse.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.