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


424

У своїй програмі я використовую сторонню бібліотеку (точні дані Spring для MongoDB).

Методи цієї бібліотеки повертаються Iterable<T>, а решта мого коду очікує Collection<T>.

Чи є десь корисний метод, який дозволить мені швидко перетворити один в інший? Я хотів би уникнути створення купок foreachциклів у своєму коді для такої простої речі.


3
Будь-який метод використання для виконання операції зобов'язаний повторити збірку, так що ви не можете очікувати збільшення продуктивності. Але якщо ви просто шукаєте синтаксичний цукор, я б поїхав на колекції Guava або, можливо, Apache Collection.
Себастьян Гансленд

" все одно пов'язаний з повторенням колекції ", - ні, це не так. Детальну інформацію див. У моїй відповіді.
aioobe

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

Відповіді:


387

За допомогою Guava ви можете використовувати Lists.newArrayList (Iterable) або Sets.newHashSet (Iterable) серед інших подібних методів. Це, звичайно, скопіює всі елементи в пам'ять. Якщо це не прийнятно, я думаю, що ваш код, який працює з цими, повинен брати, Iterableа не Collection. Також Guava пропонує зручні методи для виконання тих, що ви можете робити, 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, чому б просто не перейти з типом коваріантного списку? Це дає змогу задовольнити більше контрактів без обмеження чи перекомпонування, а Java у будь-якому разі не підтримує межі нижчого типу.
Джонатан Нойфельд

@JonathanNeufeld чи чому просто не продовжувати та повертати ArrayList <T>?
Хуан

5
@Juan Тому що це не дуже SOLID . ArrayList розкриває деталі реалізації, які, швидше за все, не потрібні (YAGNI), що порушує принципи інверсії відповідальності та залежності. Я залишив би це у списку, тому що він виставляє трохи більше, ніж колекція, залишаючись повністю твердим. Якщо ви турбуєтесь про вплив коду INVOKEINTERFACE на продуктивність JVM над INVOKEVIRTUAL, безліч еталонів виявить, що втрачати сон не варто.
Джонатан Нойфельд

80

Коротке рішення з 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

ця проблема може стати новою прийнятою відповіддю - Приємно уникати зайвих лайків (наприклад, Guava).
java-addict301

48

IteratorUtilsз commons-collectionsможе допомогти (хоча вони не підтримують генерики в останньої стабільної версії 3.2.1):

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

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

Оновлення: Перевірте IterableAsListвід кактусів .


5
Але для цього потрібен Ітератор, а не Ітерабельний
hithwen

5
@hithwen, я не розумію - Iterable надає ітератор (як детально у відповіді) - у чому проблема?
Том

Не знаю, про що я думав ^^ U
hithwen

2
З 4.1 існує також IterableUtils.toList(Iterable)зручний метод, який використовується IteratorUtilsпід кришкою, але також є безпечним для нуля (на відміну від цього IteratorUtils.toList).
Yoory N.

21

З колекціїUtils :

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 () не потрібно ніколи повертати помилкове. Таким чином, у загальному випадку ви не можете розраховувати, що зможете перетворити кожну Iterable в колекцію. Наприклад, ви можете перебирати будь-які позитивні натуральні числа, повторювати щось із циклами, що дають однакові результати знову і знову, і т.д.

Інакше: відповідь Атрі досить точна.


1
Хтось насправді натрапляв на 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.
ДарійL

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

2

Два зауваження

  1. Немає необхідності перетворювати Iterable в Collection для використання циклу foreach - Iterable може використовуватися безпосередньо в такому циклі, немає синтаксичної різниці, тому я навряд чи розумію, чому взагалі задавали оригінальне питання.
  2. Запропонований спосіб перетворення Iterable у Collection є небезпечним (те саме стосується CollectionUtils) - немає гарантії, що наступні виклики методу next () повертають різні екземпляри об'єкта. Більше того, ця проблема не є чисто теоретичною. Наприклад, ітерабельна реалізація, що використовується для передачі значень методу зменшення Hadoop Reducer, завжди повертає один і той же екземпляр значення, лише з різними значеннями поля. Тож якщо застосувати makeCollection зверху (або CollectionUtils.addAll (Iterator)), ви отримаєте колекцію з усіма однаковими елементами.

2

Я натрапив на подібну ситуацію, намагаючись отримати бонус в Listпро Projectе, а не за замовчуванням 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();
}

Думаю, це найпростіше рішення, не вимагаючи логіки перетворення або використання зовнішніх бібліотек.



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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.