Получить размер Iterable в Java

88

Мне нужно выяснить количество элементов Iterableв Java. Я знаю, что могу это сделать:

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

Я также мог бы сделать что-то подобное, потому что мне больше не нужны объекты в Iterable:

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

Небольшой тест не показал большой разницы в производительности, никаких комментариев или других идей по этой проблеме?

js84
источник
Зачем? Либо - действительно плохая идея, так как оба O (N). Вам следует избегать повторения коллекции дважды.
Marquis of Lorne
3
Ваш второй не должен работать. Вы не можете позвонить, remove()не позвонив next()заранее.
Луи Вассерман,

Ответы:

123

TL; DR: используйте служебный метод Iterables.size(Iterable)великой библиотеки Guava .

Из ваших двух фрагментов кода вы должны использовать первый, потому что второй удалит все элементы values, поэтому впоследствии он будет пустым. Изменение структуры данных для простого запроса, например его размера, является очень неожиданным.

Для производительности это зависит от вашей структуры данных. Если это, например, на самом деле ArrayList, удаление элементов с самого начала (что делает ваш второй метод) очень медленное (вычисление размера становится O (n * n) вместо O (n), как должно быть).

В общем, если есть вероятность, что valuesэто действительно a, Collectionа не только Iterable, проверьте это и позвоните size()в случае:

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

Вызов size(), как правило, гораздо быстрее , чем подсчет количества элементов, и этот трюк именно Iterables.size(Iterable)из гуавы делает для вас.

Филипп Вендлер
источник
Он говорит, что его не интересуют элементы и его не волнует, удаляются ли они.
aioobe
Небольшое уточнение: он удалит элементы изvalues
Miquel
1
Но другие части кода могут по-прежнему использовать тот же Iterable. И если не сейчас, то, может быть, в будущем.
Philipp Wendler
Я предлагаю сделать ответ Google Guava более заметным. Нет причин заставлять людей писать этот код еще раз, даже если это тривиально.
Стивен Харрисон,
8
Если вы используете Java 8, создайте Stream и подсчитайте в нем элемент как: Stream.of (myIterable) .count ()
FrankBr,
41

Если вы работаете с java 8, вы можете использовать:

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

он будет работать только в том случае, если итеративный источник имеет определенный размер. Большинство Spliterator для коллекций будут, но у вас могут возникнуть проблемы, если они исходят, например, из a HashSetили ResultSet.

Вы можете проверить здесь javadoc.

Если Java 8 не подходит или вы не знаете, откуда берется итерация, вы можете использовать тот же подход, что и 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;
    }
Арно
источник
1
Кто-нибудь, вручите этому парню медаль.
Pramesh Bajracharya
У меня не работает сортировка . Ответ New Bee у меня работает.
Сабир Хан,
18

Возможно, это немного поздно, но может кому-то помочь. Я столкнулся с аналогичной проблемой Iterableв моей кодовой базе, и решение было использовать for eachбез явного вызова values.iterator();.

int size = 0;
for(T value : values) {
   size++;
}
пилот
источник
2
Для меня это интуитивный подход, который я могу оценить. Просто использовал его несколько минут назад. Спасибо.
Мэтт Креминс,
2
Да, это интуитивно понятно, но, к сожалению, вы должны подавить неиспользованное предупреждение ради ценности ...
Нильс-о-мат
Спасибо за это решение, оно действительно возвращает размер объекта. Единственная обратная сторона заключается в том, что если вы хотите повторить итерацию позже через тот же объект, сначала вы должны как-то сбросить его.
Zsolti
7

Вы можете привести свою итерацию к списку, а затем использовать для нее .size ().

Lists.newArrayList(iterable).size();

Для ясности, вышеупомянутый метод потребует следующего импорта:

import com.google.common.collect.Lists;
закуски
источник
2
Lists - это класс Guava
Пол Джексон
6

Строго говоря, у Iterable нет размера. Думайте о структуре данных как о цикле.

И подумайте о следующем экземпляре Iterable, без размера:

    new Iterable(){

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

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

                @Override
                public Object next() {
                    return fetchDataFromExternalSystem();
                }};
        }};
卢 声 远 Шэнъюань Лу
источник
2

Я бы пошел по it.next()простой причине, которая next()гарантированно будет реализована, а remove()это необязательная операция.

E next()

Возвращает следующий элемент в итерации.

void remove()

Удаляет из базовой коллекции последний элемент, возвращенный итератором (необязательная операция) .

aioobe
источник
Хотя этот ответ технически правильный, он вводит в заблуждение. Вызов removeуже отмечался как неправильный способ подсчета элементов Iterator, поэтому на самом деле не имеет значения, реализовано то, что мы не собираемся делать, или нет.
Стивен Харрисон,
1
Какая именно часть ответа вводит в заблуждение? И в том случае, когда remove он реализован, почему было бы неправильно его использовать? Кстати, отрицательные голоса обычно используются для неправильных ответов или ответов, которые дают плохой совет. Я не понимаю, насколько этот ответ подходит для чего-либо из этого.
aioobe
2

java 8 и выше

StreamSupport.stream(data.spliterator(), false).count();
Новая пчела
источник
0

Как по мне, это просто разные методы. Первый оставляет объект, который вы повторяете, без изменений, а секунды оставляют его пустым. Вопрос в том, что ты хочешь делать. Сложность удаления зависит от реализации вашего итеративного объекта. Если вы используете Коллекции - просто получите размер, подобный предложенному Казекаге Гаара - обычно это лучший подход с точки зрения производительности.

Марк Брамник
источник
-2

Почему бы вам просто не использовать свой size()метод, Collectionчтобы получить количество элементов?

Iterator предназначен только для итерации, ничего больше.

Казекаге Гаара
источник
4
Кто сказал, что пользуется коллекцией? :-)
aioobe
Вы используете итератор, который поддерживает удаление элементов. Если вы выбираете размер перед входом в цикл итератора, вам нужно быть осторожным с удалением и соответствующим образом обновить значение.
Miquel
1
Пример того, почему у него может не быть коллекции для определения размера: он мог бы вызвать сторонний метод API, который возвращает только Iterable.
SteveT
2
Этот ответ смущает Iteratorи Iterable. См. Правильный ответ на голосование.
Стивен Харрисон,
-4

Вместо использования циклов и подсчета каждого элемента или использования сторонней библиотеки мы можем просто указать тип итерации в ArrayList и получить его размер.

((ArrayList) iterable).size();
Фатимасаджад
источник
3
Не все итерируемые объекты можно преобразовать в ArrayList, хотя
Александр Миллс