Простой способ конвертировать Iterable в коллекцию


424

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

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

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


3
Любой используемый метод выполнения операции в любом случае связан с итерацией коллекции, поэтому вы не можете ожидать какого-либо прироста производительности. Но если вы просто ищете синтаксический сахар, я бы выбрал Guava или, возможно, Apache Collections.
Себастьян Гансландт

«В любом случае обязан перебирать коллекцию », - нет, это не так. Смотрите мой ответ для деталей.
aioobe

3
в вашем конкретном случае вы можете просто расширить CrudRepository своим собственным интерфейсом с методами, которые возвращают Collection <T> / List <T> / Set <T> (при необходимости) вместо Iterable <T>
Кевин Ван Дейк

Ответы:


387

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


1
Перебирает ли он все элементы напрямую? Т.е. Lists.newArrayList(Iterable).clear()это линейная или постоянная операция по времени?
aioobe

2
@aioobe: создает копию итерации. Не было указано, что представление было желательным, и учитывая, что большинство методов Collectionлибо не могут быть реализованы для представления, Iterableлибо не будут эффективными, для меня не имеет особого смысла делать это.
ColinD

@ColinD что, если я хочу посмотреть? На самом деле, что я хочу, это представление коллекции, которое является результатом добавления исходной коллекции с другим элементом. Я могу использовать, Iterables.concat()но это дает Iterable, а не Collection:(
Хенди Ираван

1
Это мой вопрос: stackoverflow.com/questions/4896662/… . К сожалению, простой ответ, который не решает проблему, заключается в использовании Iterables.concat(). Более длинный ответ дает Collection... Интересно, почему это не поддерживается чаще?
Хенди Ираван

365

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

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

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

iterable.forEach(target::add);

86
Илиiterable.forEach(target::add);
Головоногий

92

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

public static <E> Collection<E> makeCollection(Iterable<E> iter) {
    Collection<E> list = new ArrayList<E>();
    for (E item : iter) {
        list.add(item);
    }
    return list;
}

33
+1 Если переход от Iterableк Collection- единственная проблема, я предпочел бы этот подход по сравнению с импортом большой сторонней библиотеки коллекций.
aioobe

2
4 строки кода функции гораздо предпочтительнее, чем 2 МБ скомпилированного библиотечного кода, для которого 99% не используется. Есть и другая цена: сложности с лицензированием. Лицензия Apache 2.0 является гибкой, но не без утомительных мандатов. В идеале мы увидим, что некоторые из этих общих шаблонов интегрированы непосредственно в библиотеки времени выполнения Java.
Джонатан Нойфельд

2
Еще один момент: поскольку вы все равно используете ArrayList, почему бы просто не использовать вместо этого ковариантный тип List? Это позволяет вам удовлетворять больше контрактов без приведения или перекомпоновки, и в любом случае Java не поддерживает нижние границы типов.
Джонатан Нойфельд

@JonathanNeufeld или почему бы просто не вернуться и вернуть ArrayList <T>?
Хуан

5
@Juan Потому что это не очень твердо . ArrayList предоставляет детали реализации, которые, скорее всего, не нужны (YAGNI), что нарушает принципы единой ответственности и инверсии зависимостей. Я бы оставил его в List, потому что он показывает немного больше, чем Collection, оставаясь при этом полностью твердым. Если вас беспокоит влияние на производительность JVM кода операции INVOKEINTERFACE по сравнению с INVOKEVIRTUAL, множество тестов покажут, что не стоит терять сон.
Джонатан Нойфельд

81

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

public static <T> List<T> toList(final Iterable<T> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false)
                        .collect(Collectors.toList());
}

1
этот подход слишком медленный по сравнению IteratorUtilsсcommons-collections
Алекс Бурдусел

3
Насколько медленнее? IteratorUtils.toList()использует итератор до Java 5, чтобы добавить элементы один за другим во вновь созданный список. Простой и, возможно, самый быстрый, но добавляет 734 кБ к вашему бинарному файлу, и вы можете сделать это самостоятельно, если сочтете этот метод лучшим.
xehpuk

8
Я сделал примитивный тест, заключив, что иногда первый быстрее, а второй быстрее. Покажите нам свой эталон.
xehpuk

этот вопрос может быть новым принятым ответом - хорошо избегать лишних библиотек (например, гуавы).
java-addict301

48

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

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

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

Обновление: проверка IterableAsListот Cactoos .


5
Но для этого нужен итератор, а не
итеративный

5
@hithwen, я не понимаю - Iterable предоставляет Iterator (как подробно описано в ответе) - в чем проблема?
Том

Не знаю, о чем я думал ^^ U
hithwen

2
Начиная с версии 4.1 IterableUtils.toList(Iterable), этот метод удобен и используется IteratorUtilsпод капотом, но также является нулевым (в отличие от него IteratorUtils.toList).
Йори Н.

21

Из 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());
    }
}

Перебирает ли он все элементы напрямую? Т.е. Lists.newArrayList(someIterable).clear()это линейная или постоянная операция по времени?
aioobe

Я добавил исходный код addAll, как видно из названия, он копирует значения итератора одно за другим; он создает копию, а не представление.
Томаш Нуркевич

Как жаль, что нет способа CollectionUtilsпропустить создание коллекции в дополнительной строке.
Карл Рихтер

Неработающая ссылка ☝️☝️
Hola Soy Edu Feliz Navidad

14

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

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

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

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

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

В противном случае: ответ Атри вполне в порядке.


1
Кто-нибудь когда-нибудь сталкивался с Iterable, который перебирает нечто бесконечное (например, пример натуральных чисел, приведенный в ответе), на практике / в реальном коде? Я думаю, что такой Iterable вызовет боль и горе во многих местах ... :)
Дэвид

2
@David Хотя я не могу конкретно указать на бесконечный итератор в любом из моего производственного кода, я могу вспомнить случаи, когда они могут произойти. У видеоигры может быть навык, который создает элементы в циклическом паттерне, который предлагает приведенный выше ответ. Хотя я не сталкивался с бесконечными итераторами, я определенно сталкивался с итераторами, где реальная проблема - память. У меня есть итераторы над файлами на диске. Если бы у меня был полный 1 ТБ диск и 4 ГБ оперативной памяти, я мог бы легко исчерпать память, преобразовав свой итератор в коллекцию.
radicaledward101

14

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


9
Следует отметить, что это тоже из Гуавы.
Вадим

Или из org.apache.commons.collections4. Тогда это FluentIterable.of (myIterable) .toList ()
du-it

9

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


2
Я бы рекомендовал использовать универсальный CrudRepository, чтобы избежать привязки вашего кода к конкретной реализации.
stanlick

7

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

Главный:

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

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

тесты:

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

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


1
Ваш летний ответ - основа нового вопроса stackoverflow.com/questions/32570534/…, привлекающего множество просмотров и комментариев.
Пол Боддингтон

6

Как только вы звоните contains, containsAll, equals, hashCode, remove, retainAll, sizeили toArray, вы должны пройти через элементы в любом случае.

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

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



6

В 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.


5

Так как RxJava - это молот, а это похоже на гвоздь, вы можете сделать

Observable.from(iterable).toList().toBlocking().single();

23
есть ли какой-нибудь способ вовлечь jquery возможно?
Дмитрий Минковский

3
сбой, если в RxJava есть нулевой элемент. не так ли?
MBH

Я считаю, что RxJava2 не разрешает нулевые элементы, должно быть хорошо в RxJava.
DariusL

4

Вот 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);
    }
}

4

Я столкнулся с подобной ситуацией при попытке получить a 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();
}

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


2

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

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


1

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

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

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

0

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

Вы также можете преобразовать в Iterablea 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 Collections.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.