105

I need to figure out the number of elements in an Iterable in Java. I know I can do this:

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

I could also do something like this, because I do not need the objects in the Iterable any further:

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

A small scale benchmark did not show much performance difference, any comments or other ideas for this problem?

Philipp Wendler
  • 11,184
  • 7
  • 52
  • 87
js84
  • 3,676
  • 2
  • 19
  • 23

10 Answers10

144

TL;DR: Use the utility method Iterables.size(Iterable) of the great Guava library.

Of your two code snippets, you should use the first one, because the second one will remove all elements from values, so it is empty afterwards. Changing a data structure for a simple query like its size is very unexpected.

For performance, this depends on your data structure. If it is for example in fact an ArrayList, removing elements from the beginning (what your second method is doing) is very slow (calculating the size becomes O(n*n) instead of O(n) as it should be).

In general, if there is the chance that values is actually a Collection and not only an Iterable, check this and call size() in case:

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

The call to size() will usually be much faster than counting the number of elements, and this trick is exactly what Iterables.size(Iterable) of Guava does for you.

Philipp Wendler
  • 11,184
  • 7
  • 52
  • 87
  • He says that he's not interested in the elements and doesn't care if they are removed though. – aioobe Jul 22 '12 at 09:13
  • Small clarification: it will remove the elements *from `values`* – Miquel Jul 22 '12 at 09:13
  • 1
    But other parts of the code might still be using the same Iterable. And if not now, perhaps in the future. – Philipp Wendler Jul 22 '12 at 09:14
  • I suggest making the Google Guava answer more prominent. There's no reason to make people write this code again, even though it's trivial. – Stephen Harrison Aug 15 '15 at 19:27
  • @StephenHarrison Thanks, this is a good idea, I edited my post accordingly. – Philipp Wendler Aug 15 '15 at 20:31
  • 9
    If you use Java 8, create a Stream and count element in it as: Stream.of(myIterable).count() – FrankBr Aug 22 '16 at 07:16
  • Just beware that calling this on an Iterable object that has been assigned will not return the same result twice: List list = new ArrayList(); list.add("yo"); list.add("blah"); list.add("ok"); System.out.println("1st call: "+Iterators.size(list.iterator())); System.out.println("2nd call: "+Iterators.size(list.iterator())); Iterator iterObj = list.iterator(); System.out.println("1st call on obj: "+Iterators.size(iterObj)); System.out.println("2nd call on obj: "+Iterators.size(iterObj)); yields 1st call: 3 2nd call: 3 1st call on obj: 3 2nd call on obj: 0 – mancini0 Jun 01 '17 at 16:52
  • @mancini0 You are confusing Iterables and Iterators. This question is about Iterables, and for Iterables everything works fine. Your example code demonstrates only that the similar utility method that happens to exist for Iterators exhausts the Iterator, but this has nothing to do with the code from this answer. – Philipp Wendler Jun 01 '17 at 17:34
  • What if my iterator is like ```Iterator``` – EXODIA Feb 02 '21 at 13:43
51

If you are working with java 8 you may use:

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

it will only work if the iterable source has a determined size. Most Spliterators for Collections will, but you may have issues if it comes from a HashSetor ResultSetfor instance.

You can check the javadoc here.

If Java 8 is not an option, or if you don't know where the iterable comes from, you can use the same approach as guava:

  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
  • 1,954
  • 18
  • 19
19

This is perhaps a bit late, but may help someone. I come across similar issue with Iterable in my codebase and solution was to use for each without explicitly calling values.iterator();.

int size = 0;
for(T value : values) {
   size++;
}
pilot
  • 866
  • 11
  • 16
  • 2
    This is, to me, an intuitive approach, which I can appreciate. Just used it a few minutes ago. Thanks. – Matt Cremeens Sep 14 '15 at 15:07
  • 2
    Yes it's intuitive, but sadly you have to suppress an unused-warning for value... – Nils-o-mat May 11 '17 at 15:15
  • Thanks for this solution, it indeed returns the size of the object. The only backside is that if you want to iterate again later through the same object, then first you should reset it somehow. – Zsolti Jan 30 '18 at 16:22
9

You can cast your iterable to a list then use .size() on it.

Lists.newArrayList(iterable).size();

For the sake of clarity, the above method will require the following import:

import com.google.common.collect.Lists;
stratagem
  • 525
  • 6
  • 7
6

Strictly speaking, Iterable does not have size. Think data structure like a cycle.

And think about following Iterable instance, No size:

    new Iterable(){

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

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

                @Override
                public Object next() {
                    return fetchDataFromExternalSystem();
                }};
        }};
卢声远 Shengyuan Lu
  • 31,208
  • 22
  • 85
  • 130
4

java 8 and above

StreamSupport.stream(data.spliterator(), false).count();
New Bee
  • 390
  • 3
  • 10
2

I would go for it.next() for the simple reason that next() is guaranteed to be implemented, while remove() is an optional operation.

E next()

Returns the next element in the iteration.

void remove()

Removes from the underlying collection the last element returned by the iterator (optional operation).

aioobe
  • 413,195
  • 112
  • 811
  • 826
  • While this answer is _technically_ correct, it's quite misleading. Calling `remove` has already been noted as the wrong way to count elements of an `Iterator`, so it doesn't really matter whether the thing we're not going to do is implemented or not. – Stephen Harrison Aug 15 '15 at 19:30
  • 1
    Exactly which part of the answer is misleading? And under the circumstance where `remove` *is* implemented, why would it be wrong to use it? Btw, downvotes are typicallt used for wrong answers or answers that give bad advice. I can't see how this answer qualifies for any of that. – aioobe Aug 15 '15 at 21:37
0

As for me, these are just different methods. The first one leaves the object you're iterating on unchanged, while the seconds leaves it empty. The question is what do you want to do. The complexity of removing is based on implementation of your iterable object. If you're using Collections - just obtain the size like was proposed by Kazekage Gaara - its usually the best approach performance wise.

Mark Bramnik
  • 39,963
  • 4
  • 57
  • 97
-2

Why don't you simply use the size() method on your Collection to get the number of elements?

Iterator is just meant to iterate,nothing else.

Kazekage Gaara
  • 14,972
  • 14
  • 61
  • 108
  • 4
    Who says he's using a collection? :-) – aioobe Jul 22 '12 at 09:11
  • You are using an iterator, which supports removal of elements. If you pick up the size before entering the iterator loop, you need to be careful with your removals and update the value accordingly. – Miquel Jul 22 '12 at 09:12
  • 1
    An example of why he may not have a collection to look at for size: he could be calling a third-party API method that returns only an Iterable. – SteveT Nov 15 '12 at 21:43
  • 2
    This answer confuses `Iterator` and `Iterable`. See the voted answer for the correct way. – Stephen Harrison Aug 15 '15 at 19:26
-4

Instead of using loops and counting each element or using and third party library we can simply typecast the iterable in ArrayList and get its size.

((ArrayList) iterable).size();
fatimasajjad
  • 605
  • 8
  • 16