Obtenha o tamanho de um Iterable em Java

90

Preciso descobrir o número de elementos em um Iterableem Java. Eu sei que posso fazer isso:

Iterable values = ...
it = values.iterator();
while (it.hasNext()) {
  it.next();
  sum++;
}

Eu também poderia fazer algo assim, porque não preciso mais dos objetos no Iterable:

it = values.iterator();
while (it.hasNext()) {
  it.remove();
  sum++;
}

Um benchmark de pequena escala não mostrou muita diferença de desempenho, algum comentário ou outras ideias para este problema?

js84
fonte
Por quê? Qualquer uma das duas é uma ideia muito ruim, já que ambas são O (N). Você deve tentar evitar ter que iterar a coleção duas vezes.
user207421
3
O seu segundo nem deveria funcionar. Você não pode ligar remove()sem ligar de next()antemão.
Louis Wasserman

Respostas:

125

TL; DR: Use o método utilitário Iterables.size(Iterable)da grande biblioteca Guava .

Dos seus dois snippets de código, você deve usar o primeiro, porque o segundo removerá todos os elementos de values , portanto, ele fica vazio depois. Alterar a estrutura de dados de uma consulta simples como seu tamanho é muito inesperado.

Para desempenho, isso depende de sua estrutura de dados. Se for, por exemplo, de fato um ArrayList, remover elementos do início (o que seu segundo método está fazendo) é muito lento (calcular o tamanho torna-se O (n * n) em vez de O (n) como deveria ser).

Em geral, se houver a chance de que valuesseja realmente um Collectione não apenas um Iterable, verifique e chame size()neste caso:

if (values instanceof Collection<?>) {
  return ((Collection<?>)values).size();
}
// use Iterator here...

A chamada para size()geralmente será muito mais rápido do que contar o número de elementos, e esse truque é exatamente o Iterables.size(Iterable)de goiaba faz por você.

Philipp Wendler
fonte
Ele diz que não está interessado nos elementos e não se importa se eles forem removidos.
aioobe
Pequeno esclarecimento: irá remover os elementos devalues
Miquel
1
Mas outras partes do código ainda podem estar usando o mesmo Iterable. E se não for agora, talvez no futuro.
Philipp Wendler
Eu sugiro dar mais destaque à resposta do Google Guava. Não há razão para fazer as pessoas escreverem esse código novamente, mesmo que seja trivial.
Stephen Harrison de
8
Se você usar Java 8, crie um Stream e conte o elemento nele como: Stream.of (myIterable) .count ()
FrankBr
41

Se estiver trabalhando com o java 8, você pode usar:

Iterable values = ...
long size = values.spliterator().getExactSizeIfKnown();

ele só funcionará se a fonte iterável tiver um tamanho determinado. A maioria dos Divisores para Coleções sim, mas você pode ter problemas se vier de um HashSetou, ResultSetpor exemplo.

Você pode verificar o javadoc aqui.

Se o Java 8 não for uma opção , ou se você não souber de onde vem o iterável, você pode usar a mesma abordagem da goiaba:

  if (iterable instanceof Collection) {
        return ((Collection<?>) iterable).size();
    } else {
        int count = 0;
        Iterator iterator = iterable.iterator();
        while(iterator.hasNext()) {
            iterator.next();
            count++;
        }
        return count;
    }
ArnaudR
fonte
1
Alguém, dê uma medalha a esse cara.
Pramesh Bajracharya
Não funciona para mim para Sort . A resposta de New Bee funciona para mim.
Sabir Khan de
18

Talvez seja um pouco tarde, mas pode ajudar alguém. Encontrei um problema semelhante com Iterableem minha base de código e a solução foi usar for eachsem chamar explicitamente values.iterator();.

int size = 0;
for(T value : values) {
   size++;
}
piloto
fonte
2
Esta é, para mim, uma abordagem intuitiva, que posso apreciar. Usei há poucos minutos. Obrigado.
Matt Cremeens
2
Sim, é intuitivo, mas infelizmente você tem que suprimir um aviso não utilizado para valor ...
Nils-o-mat
Obrigado por esta solução, ele realmente retorna o tamanho do objeto. A única desvantagem é que se você quiser iterar novamente mais tarde através do mesmo objeto, primeiro você deve redefini-lo de alguma forma.
Zsolti
6

A rigor, Iterable não tem tamanho. Pense na estrutura de dados como um ciclo.

E pense em seguir a instância Iterable, Sem tamanho:

    new Iterable(){

        @Override public Iterator iterator() {
            return new Iterator(){

                @Override
                public boolean hasNext() {
                    return isExternalSystemAvailble();
                }

                @Override
                public Object next() {
                    return fetchDataFromExternalSystem();
                }};
        }};
卢 声 远 Shengyuan Lu
fonte
6

Você pode converter seu iterável em uma lista e usar .size () nela.

Lists.newArrayList(iterable).size();

Por motivos de clareza, o método acima exigirá a seguinte importação:

import com.google.common.collect.Lists;
estratagema
fonte
3
Lists é uma aula de goiaba
Paul Jackson
2

Eu escolheria pelo it.next()simples motivo de que next()sua implementação é garantida, enquanto remove()é uma operação opcional.

E next()

Retorna o próximo elemento na iteração.

void remove()

Remove da coleção subjacente o último elemento retornado pelo iterador (operação opcional) .

aioobe
fonte
Embora essa resposta seja tecnicamente correta, é bastante enganosa. Chamar removejá foi apontado como a maneira errada de contar os elementos de um Iterator, então realmente não importa se o que não vamos fazer é implementado ou não.
Stephen Harrison de
1
Exatamente que parte da resposta é enganosa? E nas circunstâncias em que remove é implementado, por que seria errado usá-lo? A propósito, votos negativos são tipicamente usados ​​para respostas erradas ou respostas que dão conselhos ruins. Não consigo ver como essa resposta se qualifica para nada disso.
aioobe
2

java 8 e superior

StreamSupport.stream(data.spliterator(), false).count();
Nova abelha
fonte
0

Quanto a mim, são apenas métodos diferentes. O primeiro deixa o objeto no qual você está iterando inalterado, enquanto os segundos o deixam vazio. A questão é o que você quer fazer. A complexidade da remoção é baseada na implementação de seu objeto iterável. Se você estiver usando coleções - apenas obtenha o tamanho proposto por Kazekage Gaara - geralmente é a melhor abordagem em termos de desempenho.

Mark Bramnik
fonte
-2

Por que você simplesmente não usa o size()método em seu Collectioncomputador para obter o número de elementos?

Iterator destina-se apenas a iterar, nada mais.

Kazekage Gaara
fonte
4
Quem disse que ele está usando uma coleção? :-)
aioobe
Você está usando um iterador, que oferece suporte à remoção de elementos. Se você selecionar o tamanho antes de entrar no loop do iterador, será necessário ter cuidado com suas remoções e atualizar o valor de acordo.
Miquel
1
Um exemplo de porque ele pode não ter uma coleção para verificar o tamanho: ele pode estar chamando um método de API de terceiros que retorna apenas um Iterable.
SteveT
2
Esta resposta confunde Iteratore Iterable. Veja a resposta votada para a forma correta.
Stephen Harrison
-4

Em vez de usar loops e contar cada elemento ou usar uma biblioteca de terceiros, podemos simplesmente fazer o typecast do iterável em ArrayList e obter seu tamanho.

((ArrayList) iterable).size();
fatimasajjad
fonte
3
Nem todos os iteráveis ​​podem ser lançados para ArrayList tho
Alexander Mills