Conversão Iterator para ArrayList

241

Dado Iterator<Element>, como podemos converter que Iteratora ArrayList<Element>(ou List<Element>) na melhor e mais rápida maneira possível, de modo que podemos usar ArrayList's operações nele, como get(index), add(element), etc.

Maksim
fonte

Respostas:

360

Melhor usar uma biblioteca como o Guava :

import com.google.common.collect.Lists;

Iterator<Element> myIterator = ... //some iterator
List<Element> myList = Lists.newArrayList(myIterator);

Outro exemplo de goiaba:

ImmutableList.copyOf(myIterator);

ou Apache Commons Collections :

import org.apache.commons.collections.IteratorUtils;

Iterator<Element> myIterator = ...//some iterator

List<Element> myList = IteratorUtils.toList(myIterator);       
Renaud
fonte
8
Eu não entendo. De que maneira o ArrayList é retornado por, digamos, Goiaba melhor do que um ArrayList normal? Eles fazem isso de uma maneira mais eficiente? Mesmo se for mais eficiente, vale a pena adicionar uma dependência extra (e mais complexidade) ao seu projeto?
CorayThan
10
@CorayThan menos código + métodos testados. Embora eu concorde com você, não adicionaria uma dependência extra apenas para usar esse método. Mas, novamente, a maioria dos meus (grandes) projectos usar goiaba ou Apache Commons ...
Renaud
4
@CorayThan Divida e conquiste meu amigo. Por que escrever um método que já é fornecido por uma biblioteca e é testado? Estamos usando muitos Apache Commons e Guava, eles são incríveis e ajudam você a economizar tempo e dinheiro.
Stephan
5
Concordo com Renaud e Stephan. Além disso, se você estiver usando uma ferramenta de construção, é a coisa mais fácil do mundo incluir essas bibliotecas ... TAMBÉM ... se você realmente não quiser incluí-las, poderá acessar a fonte do código Apache / Guava e copie o que eles fizeram lá: MUITO melhor do que desperdiçar seu tempo reinventando as centenas de rodas lindamente projetadas e testadas que já estão lá fora.
mike roedor
238

No Java 8, você pode usar o novo forEachRemainingmétodo que foi adicionado à Iteratorinterface:

List<Element> list = new ArrayList<>();
iterator.forEachRemaining(list::add);
Stuart Marks
fonte
2
o que significa ::? que nome tem isso? parece uma referência direta a list.add (); e parece também alguma coisa nova em java8; e obrigado! :)
Poder de Aquário
13
@AquariusPower A ::sintaxe é nova no Java 8 e refere-se a uma "referência de método", que é uma forma abreviada de uma lambda. Veja aqui para obter mais informações: docs.oracle.com/javase/tutorial/java/javaOO/…
Stuart Marks
de onde iteratorvem? é um símbolo não resolvido
javadba
1
@javadba A pergunta original era sobre como converter um iterador que você já possui em um novo ArrayList. Para os propósitos deste exemplo, presume-se que o iterador que você já possui seja chamado iterator.
Stuart Marks
1
Esta deve ser a resposta aceita, como é o mais curto e não depende de bibliotecas de 3rd party
DanielCuadra
64

Você pode copiar um iterador para uma nova lista como esta:

Iterator<String> iter = list.iterator();
List<String> copy = new ArrayList<String>();
while (iter.hasNext())
    copy.add(iter.next());

Isso pressupõe que a lista contenha cadeias. Realmente não existe uma maneira mais rápida de recriar uma lista a partir de um iterador, você deve atravessá-la manualmente e copiar cada elemento para uma nova lista do tipo apropriado.

EDIT:

Aqui está um método genérico para copiar um iterador para uma nova lista de uma maneira segura para o tipo:

public static <T> List<T> copyIterator(Iterator<T> iter) {
    List<T> copy = new ArrayList<T>();
    while (iter.hasNext())
        copy.add(iter.next());
    return copy;
}

Use-o assim:

List<String> list = Arrays.asList("1", "2", "3");
Iterator<String> iter = list.iterator();
List<String> copy = copyIterator(iter);
System.out.println(copy);
> [1, 2, 3]
Óscar López
fonte
26

Observe que há uma diferença entre Iterablee Iterator.

Se você possui um Iterable, então com o Java 8 você pode usar esta solução:

Iterable<Element> iterable = createIterable();
List<Element> array = StreamSupport
    .stream(iterable.spliterator(), false)
    .collect(Collectors.toList());

Como eu sei Collectors.toList()cria ArrayListinstância.

Na verdade, na minha opinião, também fica bem em uma linha.
Por exemplo, se você precisar retornar List<Element>de algum método:

return StreamSupport.stream(iter.spliterator(), false).collect(Collectors.toList());
Dub Nazar
fonte
4
A questão é sobre o Iterador <Element> como ponto de partida, não o Iterável <Element>.
21417 Jaap
1
concorda com o comentário acima. super confuso que você nomeou seu Iterable iterator. Eles são completamente diferentes.
Stephen Harrison
No Java 8, você pode facilmente converter uma Iteratorvolta para um único uso Iterable usando () -> iterator. Isso pode ser útil em situações como essa, mas deixe-me enfatizar o importante novamente: você pode usar esse método apenas se um único uso Iterable for aceitável. Ligar iterable.iterator()mais de uma vez produzirá resultados inesperados. A resposta acima é entãoIterator<Element> iterator = createIterator(); List<Element> array = StreamSupport.stream(((Iterable<Element>) () -> iterable).spliterator(), false).collect(toList());
neXus
9

Solução bastante concisa com Java 8 simples usando java.util.stream:

public static <T> ArrayList<T> toArrayList(final Iterator<T> iterator) {
    return StreamSupport
        .stream(
            Spliterators
                .spliteratorUnknownSize(iterator, Spliterator.ORDERED), false)
        .collect(
                Collectors.toCollection(ArrayList::new)
    );
}
xehpuk
fonte
Existe uma maneira mais compacta de escrever isso usando a API de fluxo? Parece não ser mais simples que o normal, enquanto o loop while.
xi.lin
37
Eu não chamaria essa solução de "concisa".
1516 Sergio Sergio
1
@Sergio É por isso que escrevi "bonita". No entanto, ele não precisa de variáveis ​​locais e apenas um ponto e vírgula. Você pode reduzi-lo com importações estáticas.
xehpuk
1
Eu diria iterator.forEachRemaining( list::add ), também novo no Java 8, é muito mais conciso. Pressionar a variável da lista para dentro Collectornão melhora a legibilidade nesse caso, pois ela precisa ser suportada por a Streame a Spliterator.
Sheepy
1
@Sergio Não é curto, mas é uma expressão única, o que faz uma enorme diferença.
michaelsnowden
5
List result = new ArrayList();
while (i.hasNext()){
    result.add(i.next());
}
Akvel
fonte
11
@LuggiMendoza: Stack Overflow não é para ser um lugar onde você pode simplesmente cortar e colar e resolver seu problema. É para ser informativo. Esta é uma resposta perfeitamente informativa e qualquer pessoa razoável deve ser capaz de concluir que o i era um iterador. Pelo que parece, você não faz quase nenhum esforço para tentar entender o que estava acontecendo.
CaTalyst.X
2

Experimente StickyListno Cactoos :

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

Disclaimer: Eu sou um dos desenvolvedores.

yegor256
fonte
Isso não responde à pergunta. Iterable! =Iterator
xehpuk
Ótimo, agora você provavelmente precisa gerar o JavaDoc para a nova versão e atualizar o link. :)
xehpuk 24/10
2

Aqui está uma lista usando Streams

Iterator<?> iterator = ...
List<?> list = StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, 0), false)
                .collect(Collectors.toList());
apflieger
fonte
1

Eu só quero apontar uma solução aparentemente óbvia que NÃO funcionará:

Lista de lista = Stream.generate (iterator :: next)
    .collect (Collectors.toList ());

Isso porque Stream#generate(Supplier<T>)pode criar apenas fluxos infinitos, não espera que seu argumento seja lançado NoSuchElementException(é o que Iterator#next()fará no final).

A resposta do xehpuk deve ser usada se a opção Iterador → Fluxo → Lista for a sua escolha.

Sasha
fonte
0

use o google goiaba !

Iterable<String> fieldsIterable = ...
List<String> fields = Lists.newArrayList(fieldsIterable);

++

fedevo
fonte
Iterador (não Iterável) para ArrayList
Chthonic Project
-2

Aqui, neste caso, se você quiser o caminho mais rápido possível, então for loopé melhor.

O iterador sobre um tamanho de amostra de 10,000 runstakes 40 msonde, quanto ao loop, leva2 ms

        ArrayList<String> alist = new ArrayList<String>();  
        long start, end;  

        for (int i = 0; i < 1000000; i++) {  
            alist.add(String.valueOf(i));  
        }  

        ListIterator<String> it = alist.listIterator();      

        start = System.currentTimeMillis();  
        while (it.hasNext()) {  
            String s = it.next();  
        }  
        end = System.currentTimeMillis();  

        System.out.println("Iterator start: " + start + ", end: " + end + ", delta: "  
            + (end - start));  
        start = System.currentTimeMillis();  
        int ixx = 0;  
        for (int i = 0; i < 100000; i++) {  
            String s = alist.get(i);  
        }  

        System.out.println(ixx);  
        end = System.currentTimeMillis();  
        System.out.println("for loop start: " + start + ", end: " + end + ", delta: "  
            + (end - start));  

Isso pressupõe que a lista contenha cadeias.

vikiiii
fonte
3
Certamente, usar um forloop e acessar os elementos de uma lista get(i)é mais rápido do que usar um iterador ... mas não era isso que o OP estava pedindo, ele mencionou especificamente que um iterador é fornecido como entrada.
Óscar López
@Oscar me desculpe. Devo excluir minha resposta?
vikiiii
Essa é a sua decisão. Eu não o excluiria ainda, pode ser informativo. Eu só apagar minhas respostas quando as pessoas começam a downvote eles :)
Óscar López
Acho que @ ÓscarLópez está certo ... essa pode não ser a resposta necessária, mas contém informações úteis para os leitores (como eu: P).
Chthonic Project
Você tem um erro na sua resposta. No get(int)loop, você está olhando apenas 100k das entradas de 1m. Se você alterar esse loop it.size(), verá que esses dois métodos estão próximos da mesma velocidade. Em geral, esses tipos de testes de velocidade do java fornecem informações limitadas sobre o desempenho na vida real e devem ser vistos com ceticismo.
Grey