Cara mudah untuk mengonversi Iterable ke Collection

424

Dalam aplikasi saya, saya menggunakan perpustakaan pihak ke-3 (Data Spring untuk MongoDB tepatnya).

Metode perpustakaan ini kembali Iterable<T>, sedangkan kode saya yang lain mengharapkan Collection<T>.

Apakah ada metode utilitas di suatu tempat yang akan membiarkan saya dengan cepat mengkonversi satu ke yang lain? Saya ingin menghindari membuat banyak foreachloop dalam kode saya untuk hal yang sangat sederhana.

Ula Krukar
sumber
3
Metode utilitas apa pun untuk melakukan operasi terikat pada iterate koleksi, sehingga Anda tidak dapat mengharapkan kenaikan kinerja. Tetapi jika Anda hanya mencari gula sintaksis saya akan memilih Jambu Biji atau Koleksi Apache.
Sebastian Ganslandt
" terikat ke iterate dari koleksi pula ", - tidak, tidak. Lihat jawaban saya untuk detailnya.
aioobe
3
di usecase spesifik Anda, Anda bisa memperluas CrudRepository dengan antarmuka Anda sendiri dengan metode yang mengembalikan Koleksi <T> / Daftar <T> / Set <T> (sesuai kebutuhan) alih-alih Iterable <T>
Kevin Van Dyck

Jawaban:

387

Dengan Guava Anda dapat menggunakan Lists.newArrayList (Iterable) atau Sets.newHashSet (Iterable) , di antara metode serupa lainnya. Ini tentu saja akan menyalin semua elemen ke dalam memori. Jika itu tidak dapat diterima, saya pikir kode Anda yang berfungsi dengan ini harus mengambil Iterablealih Collection. Jambu biji juga terjadi untuk memberikan metode yang nyaman untuk melakukan hal-hal yang dapat Anda lakukan pada Collectionmenggunakan Iterable(seperti Iterables.isEmpty(Iterable)atau Iterables.contains(Iterable, Object)), tetapi implikasi kinerja lebih jelas.

ColinD
sumber
1
Apakah itu beralih melalui semua elemen secara langsung? Yaitu, apakah Lists.newArrayList(Iterable).clear()operasi waktu linier atau konstan?
aioobe
2
@aioobe: Ini membuat salinan dari iterable. Tidak ditentukan bahwa suatu tampilan diinginkan, dan mengingat bahwa sebagian besar metode pada Collectionsalah satu tidak dapat diimplementasikan untuk tampilan Iterableatau tidak akan efisien, tidak masuk akal bagi saya untuk melakukan itu.
ColinD
@ColinD bagaimana jika saya ingin melihat? Sebenarnya, yang saya inginkan adalah tampilan Koleksi yang merupakan hasil dari menambahkan Koleksi sumber dengan elemen lain. Saya bisa menggunakan Iterables.concat()tetapi itu memberi Iterable, bukan Collection:(
Hendy Irawan
1
Ini adalah pertanyaan saya: stackoverflow.com/questions/4896662/… . Sayangnya jawaban sederhana yang tidak menyelesaikan masalah adalah dengan menggunakan Iterables.concat(). Jawaban yang lebih lama memberi Collection... Saya bertanya-tanya mengapa ini tidak lebih umum didukung?
Hendy Irawan
365

Di JDK 8+, tanpa menggunakan lib tambahan:

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

Sunting: Yang di atas adalah untuk Iterator. Jika Anda berurusan dengan Iterable,

iterable.forEach(target::add);
Thamme Gowda
sumber
86
Atauiterable.forEach(target::add);
Cephalopod
92

Anda dapat menulis metode utilitas Anda sendiri untuk ini juga:

public static <E> Collection<E> makeCollection(Iterable<E> iter) {
    Collection<E> list = new ArrayList<E>();
    for (E item : iter) {
        list.add(item);
    }
    return list;
}
Atreys
sumber
33
+1 Jika beralih dari Iterablemenjadi Collectionsatu-satunya masalah, saya lebih suka pendekatan ini daripada mengimpor perpustakaan koleksi-pihak ke-3 yang besar.
aioobe
2
4 baris kode fungsi jauh lebih disukai daripada 2 MB kode perpustakaan terkompilasi yang 99% darinya tidak digunakan. Ada biaya lain: komplikasi perizinan. Lisensi Apache 2.0 fleksibel, tetapi bukan tanpa mandat yang membosankan. Idealnya kita akan melihat beberapa pola umum ini terintegrasi langsung ke dalam perpustakaan Java runtime.
Jonathan Neufeld
2
Satu hal lagi, karena Anda menggunakan ArrayList, mengapa tidak pergi dengan tipe Daftar kovarian saja? Ini memungkinkan Anda untuk memenuhi lebih banyak kontrak tanpa down-casting atau penyusunan ulang dan Java tetap tidak memiliki dukungan untuk batas tipe yang lebih rendah.
Jonathan Neufeld 3-15
@ JonathanNeufeld atau mengapa tidak langsung saja mengembalikan ArrayList <T>?
Juan
5
@Juan Karena itu tidak terlalu SOLID . ArrayList memaparkan detail implementasi yang kemungkinan besar tidak perlu (YAGNI), yang melanggar prinsip pertanggungjawaban tunggal dan ketergantungan. Saya akan meninggalkannya di Daftar karena itu mengekspos sedikit lebih banyak daripada Koleksi sementara tetap benar-benar SOLID. Jika Anda khawatir tentang dampak kinerja JVM dari opcode INVOKEINTERFACE lebih dari INVOKEVIRTUAL, banyak tolok ukur akan mengungkapkan bahwa tidak layak untuk tidur lebih lama.
Jonathan Neufeld
81

Solusi ringkas dengan Java 8 menggunakan java.util.stream:

public static <T> List<T> toList(final Iterable<T> iterable) {
    return StreamSupport.stream(iterable.spliterator(), false)
                        .collect(Collectors.toList());
}
xehpuk
sumber
1
pendekatan ini terlalu lambat dibandingkan dengan IteratorUtilsdaricommons-collections
Alex Burdusel
3
Seberapa lambat? IteratorUtils.toList()menggunakan iterator dalam mode Java 5 pra untuk menambahkan elemen satu per satu ke daftar yang baru dibuat. Sederhana dan mungkin tercepat, tetapi menambahkan 734 kB ke biner Anda dan Anda bisa melakukannya sendiri jika Anda menemukan metode ini sebagai yang terbaik.
xehpuk
8
Saya telah melakukan tolok ukur primitif yang menyimpulkan bahwa terkadang yang pertama lebih cepat, terkadang yang kedua lebih cepat. Tunjukkan pada kami patokan Anda.
xehpuk
prob ini bisa menjadi jawaban yang diterima baru - Sangat menyenangkan untuk menghindari kelebihan libs (seperti Jambu biji).
java-addict301
48

IteratorUtilsfrom commons-collectionsdapat membantu (meskipun mereka tidak mendukung obat generik dalam versi stabil terbaru 3.2.1):

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

Versi 4.0 (yang ada di SNAPSHOT saat ini) mendukung obat generik dan Anda dapat menyingkirkannya @SuppressWarnings.

Pembaruan: Periksa IterableAsListdari Cactoos .

yegor256
sumber
5
Tapi itu membutuhkan Iterator, bukan Iterable
sampai
5
@Hwithwen, saya tidak mengerti - Iterable menyediakan Iterator (seperti yang dijelaskan dalam jawaban) - ada apa
Tom
Tidak tahu apa yang saya pikirkan ^^ U
hithwen
2
Sejak 4.1 ada juga IterableUtils.toList(Iterable), yang merupakan metode kenyamanan dan penggunaan di IteratorUtilsbawah tenda, tetapi juga null-safe (tidak seperti IteratorUtils.toList).
Yoory N.
21

Dari CollectionUtils :

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

Berikut adalah sumber lengkap dari metode utilitas ini:

public static <T> void addAll(Collection<T> collection, Iterator<T> iterator) {
    while (iterator.hasNext()) {
        collection.add(iterator.next());
    }
}
Tomasz Nurkiewicz
sumber
Apakah itu beralih melalui semua elemen secara langsung? Yaitu, apakah Lists.newArrayList(someIterable).clear()operasi waktu linier atau konstan?
aioobe
Saya menambahkan kode sumber addAll, seperti namanya, itu menyalin nilai iterator satu demi satu; itu membuat salinan daripada tampilan.
Tomasz Nurkiewicz
Sayang sekali tidak ada metode CollectionUtilsuntuk melewati pembuatan koleksi di baris tambahan.
Karl Richter
Broken Link ☝️☝️
Kedelai Hola Edu Feliz Navidad
14

Sementara itu, jangan lupa bahwa semua koleksi terbatas, sementara Iterable tidak punya janji apa pun. Jika sesuatu Iterable Anda bisa mendapatkan Iterator dan hanya itu.

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

akan diperluas ke:

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

it.hasNext () tidak diharuskan untuk mengembalikan false. Dengan demikian dalam kasus umum Anda tidak dapat berharap untuk dapat mengkonversi setiap Iterable ke Koleksi. Misalnya Anda dapat mengulangi semua bilangan alami positif, beralih pada sesuatu dengan siklus di dalamnya yang menghasilkan hasil yang sama berulang-ulang, dll.

Jika tidak: Jawaban Atrey cukup baik.

Alexander Shopov
sumber
1
Adakah yang pernah benar-benar menemukan Iterable yang beralih pada sesuatu yang tak terbatas (seperti contoh bilangan asli yang diberikan dalam jawaban), dalam praktik / kode nyata? Saya akan berpikir bahwa Iterable seperti itu akan menyebabkan rasa sakit dan kesengsaraan di banyak tempat ... :)
David
2
@ David Meskipun saya tidak bisa secara spesifik menunjuk ke Iterator yang tak terbatas di salah satu kode produksi saya, saya bisa memikirkan kasus di mana mereka mungkin terjadi. Gim video mungkin memiliki keterampilan yang menciptakan item dalam pola siklus yang disarankan oleh jawaban di atas. Sementara saya belum menemukan iterator yang tak terbatas, saya pasti menemukan iterator di mana memori adalah masalah nyata. Saya memiliki iterator atas file pada disk. Jika saya memiliki disk 1TB penuh dan ram 4GB, saya dapat dengan mudah kehabisan memori untuk mengubah iterator saya menjadi sebuah Collection.
radicaledward101
14

Saya menggunakan FluentIterable.from(myIterable).toList()banyak.

fringd
sumber
9
Perlu dicatat bahwa itu dari Jambu juga.
Vadzim
Atau dari org.apache.commons.collections4. Maka FluentIterable.of (myIterable) .toList ()
du-it
9

Ini bukan jawaban untuk pertanyaan Anda tetapi saya yakin ini adalah solusi untuk masalah Anda. Antarmuka org.springframework.data.repository.CrudRepositorymemang memiliki metode yang mengembalikan java.lang.Iterabletetapi Anda tidak harus menggunakan antarmuka ini. Alih-alih menggunakan sub antarmuka, dalam kasus Anda org.springframework.data.mongodb.repository.MongoRepository. Antarmuka ini memiliki metode yang mengembalikan objek bertipe java.util.List.

Ludwig Magnusson
sumber
2
Saya akan mempromosikan menggunakan CrudRepository generik untuk menghindari pengikatan kode Anda dengan implementasi konkret.
stanlick
7

Saya menggunakan utilitas khusus saya untuk melemparkan Koleksi yang ada jika tersedia.

Utama:

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

Idealnya di atas akan menggunakan ImmutableList, tetapi ImmutableCollection tidak mengizinkan nol yang dapat memberikan hasil yang tidak diinginkan.

Tes:

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

Saya menerapkan utilitas serupa untuk semua subtipe Koleksi (Set, Daftar, dll). Saya pikir ini sudah menjadi bagian dari Jambu Biji, tetapi saya belum menemukannya.

Aaron Roller
sumber
1
Jawaban Anda tahun lalu adalah dasar dari pertanyaan baru stackoverflow.com/questions/32570534/... menarik banyak pandangan dan komentar.
Paul Boddington
6

Segera setelah Anda menelepon contains, containsAll, equals, hashCode, remove, retainAll, sizeatau toArray, Anda harus melintasi elemen pula.

Jika Anda sesekali hanya memanggil metode seperti isEmptyatau clearsaya kira Anda akan lebih baik dengan membuat koleksi dengan malas. Misalnya, Anda dapat memiliki dukungan ArrayListuntuk menyimpan elemen yang sebelumnya di-iterasi.

Saya tidak tahu ada kelas seperti itu di perpustakaan mana pun, tetapi itu harus menjadi latihan yang cukup sederhana untuk menulis.

aioobe
sumber
6

Di Java 8 Anda dapat melakukan ini untuk menambahkan semua elemen dari Iterableke Collectiondan mengembalikannya:

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

Terinspirasi oleh jawaban @Afreys.


sumber
5

Karena RxJava adalah palu dan ini terlihat seperti paku, Anda dapat melakukannya

Observable.from(iterable).toList().toBlocking().single();
DariusL
sumber
23
apakah ada cara untuk melibatkan jquery?
Dmitry Minkovsky
3
crash jika ada item nol di RxJava. bukan?
MBH
Saya percaya RxJava2 tidak mengizinkan item nol, harus baik-baik saja di RxJava.
DariusL
4

Berikut ini SSCCE untuk cara hebat melakukannya di 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);
    }
}
michaelsnowden
sumber
4

Aku datang di situasi yang sama ketika mencoba untuk mengambil sebuah Listdari Projects, daripada default Iterable<T> findAll()dideklarasikan dalam CrudRepositoryinterface. Jadi, di ProjectRepositoryantarmuka saya (yang memanjang dari CrudRepository), saya hanya menyatakan findAll()metode untuk mengembalikan List<Project>bukan 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();
}

Ini adalah solusi paling sederhana, saya pikir, tanpa memerlukan logika konversi atau penggunaan perpustakaan eksternal.

Manish Giri
sumber
2

Dua komentar

  1. Tidak perlu mengkonversi Iterable ke Collection untuk menggunakan foreach loop - Iterable dapat digunakan dalam loop tersebut secara langsung, tidak ada perbedaan sintaksis, jadi saya tidak mengerti mengapa pertanyaan awal ditanyakan sama sekali.
  2. Cara yang disarankan untuk mengonversi Iterable ke Collection tidak aman (hal yang sama berkaitan dengan CollectionUtils) - tidak ada jaminan bahwa panggilan selanjutnya ke metode berikutnya () mengembalikan instance objek yang berbeda. Apalagi, kekhawatiran ini tidak murni teoretis. Misalnya implementasi Iterable yang digunakan untuk meneruskan nilai ke metode pengurangan Hadoop Reducer selalu mengembalikan contoh nilai yang sama, hanya dengan nilai bidang yang berbeda. Jadi jika Anda menerapkan makeCollection dari atas (atau CollectionUtils.addAll (Iterator)), Anda akan mendapatkan koleksi dengan semua elemen yang identik.
al0
sumber
1

Coba StickyListdari Cactoos :

List<String> list = new StickyList<>(iterable);
yegor256
sumber
1

Saya tidak melihat solusi satu baris sederhana tanpa dependensi. Saya menggunakan sederhana

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

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

Anda dapat menggunakan pabrik 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);

Anda juga dapat mengonversi Iterableke a LazyIterabledan menggunakan metode konverter atau API lain yang tersedia yang tersedia.

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

Semua Mutabletipe di atas memanjang java.util.Collection.

Catatan: Saya pengendara untuk Eclipse Collections.

Donald Raab
sumber