Простой способ преобразовать Iterable в Collection

В своем приложении я использую стороннюю библиотеку (точнее, Spring Data для MongoDB).

Методы этой библиотеки возвращают Iterable<T>, тогда как остальная часть моего кода ожидает Collection<T>.

Есть ли где-нибудь служебный метод, который позволит мне быстро преобразовать одно в другое? Я бы не хотел создавать в моем коде кучу foreach циклов для такой простой вещи.


person Ula Krukar    schedule 20.06.2011    source источник
comment
Любой утилитарный метод для выполнения операции в любом случае привязан к итерации коллекции, поэтому вы не можете ожидать какого-либо прироста производительности. Но если вы просто ищете синтаксический сахар, я бы выбрал Guava или, возможно, коллекции Apache.   -  person Sebastian Ganslandt    schedule 21.06.2011
comment
все равно обязан повторять коллекцию, - нет, это не так. Подробности смотрите в моем ответе.   -  person aioobe    schedule 21.06.2011
comment
в вашем конкретном сценарии использования вы можете просто расширить CrudRepository своим собственным интерфейсом с помощью методов, которые возвращают Collection ‹T› / List ‹T› / Set ‹T› (по мере необходимости) вместо Iterable ‹T›   -  person Kevin Van Dyck    schedule 18.02.2018


Ответы (21)


С Guava вы можете использовать Lists.newArrayList (Iterable) или Наборы .newHashSet (Iterable) среди других подобных методов. Это, конечно, скопирует все элементы в память. Если это неприемлемо, я думаю, ваш код, который работает с ними, должен принимать Iterable, а не Collection. Guava также предоставляет удобные методы для выполнения действий, которые вы можете делать на Collection, используя Iterable (например, Iterables.isEmpty(Iterable) или Iterables.contains(Iterable, Object)), но последствия для производительности более очевидны.

person ColinD    schedule 20.06.2011
comment
Перебирает ли он все элементы напрямую? То есть, Lists.newArrayList(Iterable).clear() - это линейная или постоянная операция времени? - person aioobe; 21.06.2011
comment
@aioobe: он создает копию повторяемого объекта. Не было указано, что представление было желательным, и, учитывая, что большинство методов на Collection либо не могут быть реализованы для представления Iterable, либо не будут эффективными, для меня нет особого смысла делать что. - person ColinD; 21.06.2011
comment
@ColinD что, если я хочу посмотреть? На самом деле мне нужно представление коллекции, которое является результатом добавления исходной коллекции с другим элементом. Я могу использовать Iterables.concat(), но это дает Iterable, а не Collection :( - person Hendy Irawan; 10.05.2014
comment
Это мой вопрос: stackoverflow.com/questions/4896662/. К сожалению, простой ответ, который не решает проблему, - использовать Iterables.concat(). Гораздо более длинный ответ дает Collection ... Интересно, почему это обычно не поддерживается? - person Hendy Irawan; 10.05.2014

В JDK 8+ без использования дополнительных библиотек:

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

Изменить: приведенный выше для Iterator. Если вы имеете дело с Iterable,

iterable.forEach(target::add);
person Thamme Gowda    schedule 27.05.2015

Вы также можете написать свой собственный служебный метод:

public static <E> Collection<E> makeCollection(Iterable<E> iter) {
    Collection<E> list = new ArrayList<E>();
    for (E item : iter) {
        list.add(item);
    }
    return list;
}
person Atreys    schedule 20.06.2011
comment
+1 Если переход от Iterable к Collection является единственной проблемой, я бы предпочел этот подход импорту большой сторонней библиотеки коллекций. - person aioobe; 21.06.2011
comment
4 строки кода функции гораздо предпочтительнее, чем 2 МБ кода скомпилированной библиотеки, 99% которого не используются. Есть еще одна цена: сложности с лицензированием. Лицензия Apache 2.0 является гибкой, но не без утомительных мандатов. В идеале мы должны увидеть некоторые из этих общих шаблонов, интегрированных непосредственно в библиотеки времени выполнения Java. - person Jonathan Neufeld; 03.11.2015
comment
Еще один момент, поскольку вы все равно используете ArrayList, почему бы вместо этого просто не использовать ковариантный тип List? Это позволяет выполнять больше контрактов без преобразования или перекомпоновки, и Java в любом случае не поддерживает нижние границы типов. - person Jonathan Neufeld; 03.11.2015
comment
@JonathanNeufeld, или почему бы просто не вернуться и не вернуть ArrayList ‹T›? - person Juan; 06.12.2015
comment
@Juan Потому что это не очень SOLID. ArrayList раскрывает детали реализации, которые, скорее всего, не нужны (YAGNI), что нарушает принципы единой ответственности и инверсии зависимостей. Я бы оставил его в List, потому что он показывает немного больше, чем Collection, оставаясь при этом полностью SOLID. Если вас беспокоит влияние кода операции INVOKEINTERFACE на производительность JVM по сравнению с INVOKEVIRTUAL, множество тестов покажут, что не стоит терять сон. - person Jonathan Neufeld; 07.12.2015
comment
Почему не LinkedList? ArrayList неэффективен, если размер массива не определен. LinkedList сложность метода добавления составляет O (1), тогда как ArrayList сложность метода добавления составляет O (N) в худшем сценарии. ArrayList в целях увеличения копирует весь предыдущий массив и создает новый ArrayList с удвоенным размером. - person Michał Stochmal; 15.11.2018

Краткое решение с Java 8 с использованием java.util.stream:

public static <T> List<T> toList(final Iterable<T> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false)
                        .collect(Collectors.toList());
}
person xehpuk    schedule 02.06.2014
comment
этот подход слишком медленный по сравнению с IteratorUtils из commons-collections - person Alex Burdusel; 26.08.2016
comment
Насколько медленнее? IteratorUtils.toList() использует итератор в стиле до Java 5 для добавления элементов один за другим во вновь созданный список. Простой и, возможно, самый быстрый, но добавляет 734 КБ к вашему двоичному файлу, и вы можете сделать это самостоятельно, если сочтете этот метод лучшим. - person xehpuk; 26.08.2016
comment
Я сделал примитивный тест и пришел к выводу, что иногда первый быстрее, иногда второй - быстрее. Покажите нам свой ориентир. - person xehpuk; 26.08.2016
comment
эта проблема может быть новым принятым ответом - приятно избегать лишних библиотек (например, Guava). - person java-addict301; 25.10.2019

_1 _ от commons-collections может помочь (хотя они не поддерживают дженерики в последней стабильной версии 3.2.1):

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

Версия 4.0 (которая на данный момент находится в SNAPSHOT) поддерживает дженерики, и вы можете избавиться от @SuppressWarnings.

Обновление: проверьте IterableAsList из Cactoos.

person yegor256    schedule 28.04.2013
comment
Но для этого требуется Iterator, а не Iterable - person hithwen; 23.01.2015
comment
@hithwen, я не понимаю - Iterable предоставляет Iterator (как подробно описано в ответе) - в чем проблема? - person Tom; 29.02.2016
comment
Не знаю, о чем я думал ^^ U - person hithwen; 01.03.2016
comment
Начиная с версии 4.1 существует также IterableUtils.toList(Iterable), который является удобным методом и использует IteratorUtils под капотом, но также является нулевым (в отличие от IteratorUtils.toList). - person Yoory N.; 07.12.2017

Из CollectionUtils:

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

Вот полные источники этого служебного метода:

public static <T> void addAll(Collection<T> collection, Iterator<T> iterator) {
    while (iterator.hasNext()) {
        collection.add(iterator.next());
    }
}
person Tomasz Nurkiewicz    schedule 20.06.2011
comment
Перебирает ли он все элементы напрямую? То есть, Lists.newArrayList(someIterable).clear() - это линейная или постоянная операция времени? - person aioobe; 21.06.2011
comment
Я добавил исходный код addAll, как следует из названия, он копирует значения итератора одно за другим; он создает копию, а не представление. - person Tomasz Nurkiewicz; 21.06.2011
comment
Как жаль, что в CollectionUtils нет способа пропустить создание коллекции в дополнительной строке. - person Karl Richter; 03.01.2016
comment
Неработающая ссылка ☝️☝️ - person Hola Soy Edu Feliz Navidad; 24.10.2019

При этом не забывайте, что все коллекции конечны, а у Iterable нет никаких обещаний. Если что-то Iterable, вы можете получить Iterator, и все.

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

будет расширен до:

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

it.hasNext () не требуется, чтобы когда-либо возвращало false. Таким образом, в общем случае вы не можете ожидать, что сможете преобразовать каждый Iterable в Collection. Например, вы можете перебирать все положительные натуральные числа, перебирать что-то с циклами в нем, которые дают одни и те же результаты снова и снова, и т. Д.

В противном случае: ответ Атри вполне хорош.

person Alexander Shopov    schedule 30.08.2012
comment
Кто-нибудь когда-нибудь сталкивался с Iterable, который повторяет что-то бесконечное (например, пример натуральных чисел, приведенный в ответе), на практике / в реальном коде? Я бы подумал, что такой Iterable вызовет боль и проблемы во многих местах ... :) - person David; 06.05.2014
comment
@David Хотя я не могу конкретно указать на бесконечный итератор в любом из моих производственных кодов, я могу вспомнить случаи, в которых они могли бы возникнуть. У видеоигры может быть навык, который создает предметы по циклической схеме, о которой говорит приведенный выше ответ. Хотя я не встречал никаких бесконечных итераторов, я определенно встречал итераторы, в которых память является реальной проблемой. У меня есть итераторы по файлам на диске. Если у меня есть полный диск объемом 1 ТБ и 4 ГБ оперативной памяти, мне легко может не хватить памяти, преобразовав мой итератор в коллекцию. - person radicaledward101; 27.02.2015

Я использую FluentIterable.from(myIterable).toList() много.

person fringd    schedule 18.10.2013
comment
Следует отметить, что это тоже из Гуавы. - person Vadzim; 28.06.2018
comment
Или с org.apache.commons.collections4. Тогда это FluentIterable.of (myIterable) .toList () - person du-it; 06.08.2019

Я столкнулся с аналогичной ситуацией при попытке получить List из Projects, а не Iterable<T> findAll() по умолчанию, объявленный в интерфейсе CrudRepository. Итак, в моем интерфейсе ProjectRepository (который продолжается от CrudRepository) я просто объявил метод findAll() для возврата List<Project> вместо 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();
}

Думаю, это самое простое решение, не требующее логики преобразования или использования внешних библиотек.

person Manish Giri    schedule 30.05.2020

Когда вы получаете свой Iterable из Spring Data, у вас есть пара дополнительных альтернатив.

  1. Вы можете переопределить метод, который возвращает Iterable в репозитории с версией, которая возвращает List, Set или _ 5_. Таким образом, Spring Data выполняет преобразование за вас.

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

  3. Если вы используете Spring Data JPA, это уже сделано за вас в JpaRepository

  4. Вы можете выполнить преобразование, используя только что упомянутый Streamable:

    Iterable<X> iterable = repo.findAll();
    List<X> list = Streamable.of(iterable).toList();
    

И, поскольку вы упомянули, что расстроены, возможно, это немного предыстории для решения использовать Iterable помощь.

  1. Ожидается, что на самом деле очень редко требуется Collection, поэтому во многих случаях это не имеет значения.
  2. Используя механизм переопределения, можно возвращать разные типы, что было бы невозможно с более конкретным типом возврата, таким как Collection. Это сделало бы невозможным возврат Streamable, который предназначен для случаев, когда магазин может решить вернуть результат до того, как будут извлечены все элементы.
  3. Streamable на самом деле был бы гибким типом возврата, поскольку он предлагает простые преобразования в List, Set, Stream и сам по себе является Iterable. Но для этого вам потребуется использовать в своем приложении конкретный тип Spring Data, что не нравится многим пользователям.

Об этом есть раздел в справочная документация.

person Jens Schauder    schedule 06.05.2021

Это не ответ на ваш вопрос, но я считаю, что это решение вашей проблемы. Интерфейс org.springframework.data.repository.CrudRepository действительно имеет методы, возвращающие java.lang.Iterable, но вы не должны использовать этот интерфейс. Вместо этого используйте вспомогательные интерфейсы, в вашем случае org.springframework.data.mongodb.repository.MongoRepository. В этом интерфейсе есть методы, возвращающие объекты типа java.util.List.

person Ludwig Magnusson    schedule 18.06.2013
comment
Я бы рекомендовал использовать общий CrudRepository, чтобы избежать привязки вашего кода к конкретной реализации. - person stanlick; 20.11.2015

Я использую свою пользовательскую утилиту для преобразования существующей коллекции, если она доступна.

Основной:

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

В идеале вышеупомянутое должно использовать ImmutableList, но ImmutableCollection не допускает значений NULL, которые могут привести к нежелательным результатам.

Тесты:

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

Реализую аналогичные утилиты для всех подтипов Коллекций (Набор, Список и т. Д.). Я бы подумал, что это уже будет частью Guava, но я не нашел его.

person Aaron Roller    schedule 04.09.2014
comment
Ваш ответ годичной давности является основой нового вопроса stackoverflow.com/questions/32570534/, привлекая множество просмотров и комментариев. - person Paul Boddington; 15.09.2015

Как только вы вызываете contains, containsAll, equals, hashCode, remove, retainAll, size или toArray, вам все равно придется перемещаться по элементам.

Если вы время от времени вызываете только такие методы, как isEmpty или clear, я полагаю, вам будет лучше, если вы создадите коллекцию лениво. Например, у вас может быть поддержка ArrayList для хранения ранее повторяемых элементов.

Я не знаю ни одного такого класса ни в одной библиотеке, но это должно быть довольно простое упражнение для написания.

person aioobe    schedule 20.06.2011
comment
Тада .. github.com/soluvas/soluvas-framework: ) - person Hendy Irawan; 10.05.2014

В Java 8 вы можете сделать это, чтобы добавить все элементы от Iterable до Collection и вернуть его:

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

На основе ответа @Afreys.

person Community    schedule 24.04.2014

Поскольку RxJava - это молоток, и он выглядит как гвоздь, вы можете сделать

Observable.from(iterable).toList().toBlocking().single();
person DariusL    schedule 15.10.2015
comment
есть ли способ задействовать jquery? - person Dmitry Minkovsky; 09.12.2016
comment
он вылетает, если в RxJava есть нулевой элемент. не так ли? - person MBH; 17.08.2017
comment
Я считаю, что RxJava2 не допускает нулевые элементы, в RxJava все должно быть хорошо. - person DariusL; 19.08.2017

Вот SSCCE для отличного способа сделать это в 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);
    }
}
person michaelsnowden    schedule 08.08.2016

Два замечания

  1. Нет необходимости преобразовывать Iterable в Collection для использования цикла foreach - Iterable может использоваться в таком цикле напрямую, синтаксической разницы нет, поэтому я с трудом понимаю, почему вообще был задан исходный вопрос.
  2. Предлагаемый способ преобразования Iterable в Collection небезопасен (то же самое относится и к CollectionUtils) - нет гарантии, что последующие вызовы метода next () вернут разные экземпляры объекта. Более того, это опасение не чисто теоретическое. Например. Итерационная реализация, используемая для передачи значений в метод уменьшения Hadoop Reducer, всегда возвращает один и тот же экземпляр значения, только с разными значениями полей. Итак, если вы примените makeCollection сверху (или CollectionUtils.addAll (Iterator)), вы получите коллекцию со всеми идентичными элементами.
person al0    schedule 20.02.2013

Я не видел простого однострочного решения без каких-либо зависимостей. Я просто использую

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

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

Попробуйте StickyList из Cactoos:

List<String> list = new StickyList<>(iterable);
person yegor256    schedule 20.06.2017

Вы можете использовать фабрики 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);

Вы также можете преобразовать Iterable в LazyIterable и использовать методы конвертера или любые другие доступные API.

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();

Все вышеперечисленные Mutable типы расширяют java.util.Collection.

Примечание: я являюсь приверженцем коллекций Eclipse.

person Donald Raab    schedule 17.01.2020

Довольно поздно для вечеринки, но я создал очень элегантное решение для Java 8, которое позволяет преобразовывать Iterable of T в любую коллекцию T без каких-либо библиотек:

public static <T, C extends Collection<T>> C toCollection(Iterable<T> iterable, Supplier<C> baseSupplier) 
{
    C collection = baseSupplier.get();
    
    iterable.forEach(collection::add);
    
    return collection;
}

Пример использования:

Iterable<String> iterable = ...;
List<String> list = toCollection(iterable, ArrayList::new);
person David    schedule 22.02.2021