Apakah mungkin untuk melakukan Stream di Java 8?

160

Apakah mungkin untuk melakukan streaming di Java 8? Katakanlah saya memiliki daftar objek, saya dapat melakukan sesuatu seperti ini untuk menyaring semua objek tambahan:

Stream.of(objects).filter(c -> c instanceof Client)

Namun setelah ini, jika saya ingin melakukan sesuatu dengan klien saya akan perlu masing-masing:

Stream.of(objects).filter(c -> c instanceof Client)
    .map(c -> ((Client) c).getID()).forEach(System.out::println);

Ini terlihat agak jelek. Apakah mungkin untuk mengalirkan seluruh aliran ke jenis yang berbeda? Seperti dilemparkan Stream<Object>ke Stream<Client>?

Abaikan fakta bahwa melakukan hal-hal seperti ini mungkin akan berarti desain yang buruk. Kami melakukan hal-hal seperti ini di kelas ilmu komputer saya, jadi saya mencari fitur-fitur baru java 8 dan ingin tahu apakah ini mungkin.

Fiksi
sumber
3
Dari sudut pandang Java runtime kedua tipe Stream sudah sama, jadi tidak diperlukan pemeran. Caranya adalah dengan menyelinap melewati kompiler. (Yaitu, dengan asumsi masuk akal untuk melakukannya.)
Hot Licks

Jawaban:

283

Saya tidak berpikir ada cara untuk melakukan itu di luar kotak. Solusi yang mungkin lebih bersih adalah:

Stream.of(objects)
    .filter(c -> c instanceof Client)
    .map(c -> (Client) c)
    .map(Client::getID)
    .forEach(System.out::println);

atau, seperti yang disarankan dalam komentar, Anda dapat menggunakan castmetode ini - yang pertama mungkin lebih mudah dibaca:

Stream.of(objects)
    .filter(Client.class::isInstance)
    .map(Client.class::cast)
    .map(Client::getID)
    .forEach(System.out::println);
assylias
sumber
Ini adalah apa yang saya cari. Saya kira saya mengabaikan bahwa mapmengirimkannya ke Klien akan mengembalikan a Stream<Client>. Terima kasih!
Fiksi
+1 cara baru yang menarik, meskipun mereka berisiko masuk kode-spaghetti dari tipe generasi baru (horizontal, bukan vertikal)
robermann
@LordOfThePigs Ya itu berfungsi meskipun saya tidak yakin apakah kode menjadi lebih jelas. Saya telah menambahkan ide untuk jawaban saya.
assylias
38
Anda dapat "menyederhanakan" filter instanceOf dengan:Stream.of(objects).filter(Client.class::isInstance).[...]
Nicolas Labrot
Bagian tanpa panah benar-benar indah <3
Fabich
14

Sejalan dengan jawaban ggovan , saya melakukan ini sebagai berikut:

/**
 * Provides various high-order functions.
 */
public final class F {
    /**
     * When the returned {@code Function} is passed as an argument to
     * {@link Stream#flatMap}, the result is a stream of instances of
     * {@code cls}.
     */
    public static <E> Function<Object, Stream<E>> instancesOf(Class<E> cls) {
        return o -> cls.isInstance(o)
                ? Stream.of(cls.cast(o))
                : Stream.empty();
    }
}

Menggunakan fungsi pembantu ini:

Stream.of(objects).flatMap(F.instancesOf(Client.class))
        .map(Client::getId)
        .forEach(System.out::println);
Brandon
sumber
10

Terlambat ke pesta, tapi kupikir itu jawaban yang berguna.

flatMap akan menjadi cara terpendek untuk melakukannya.

Stream.of(objects).flatMap(o->(o instanceof Client)?Stream.of((Client)o):Stream.empty())

Jika oa Clientmaka buat Stream dengan elemen tunggal, jika tidak gunakan aliran kosong. Aliran ini kemudian akan diratakan menjadi a Stream<Client>.

govan
sumber
Saya mencoba menerapkan ini, tetapi mendapat peringatan yang mengatakan bahwa kelas saya "menggunakan operasi yang tidak diperiksa atau tidak aman" - apakah itu yang diharapkan?
aweibell
Sayangnya ya. Jika Anda menggunakan operator if/elsedaripada ?:operator maka tidak akan ada peringatan. Yakinlah Anda dapat dengan aman menekan peringatan.
ggovan
3
Sebenarnya ini lebih lama dari Stream.of(objects).filter(o->o instanceof Client).map(o -> (Client)o)atau bahkan Stream.of(objects).filter(Client.class::isInstance).map(Client.class::cast).
Didier L
4

Ini terlihat agak jelek. Apakah mungkin untuk mengalirkan seluruh aliran ke jenis yang berbeda? Seperti dilemparkan Stream<Object>ke Stream<Client>?

Tidak, itu tidak mungkin. Ini bukan hal baru di Java 8. Ini khusus untuk obat generik. A List<Object>bukan tipe super List<String>, jadi Anda tidak bisa hanya memberikan List<Object>aList<String> .

Serupa adalah masalah di sini. Anda tidak dapat melemparkan Stream<Object>ke Stream<Client>. Tentu saja Anda dapat melemparkannya secara tidak langsung seperti ini:

Stream<Client> intStream = (Stream<Client>) (Stream<?>)stream;

tapi itu tidak aman, dan mungkin gagal saat runtime. Alasan yang mendasari hal ini adalah, obat generik di Jawa diimplementasikan menggunakan erasure. Jadi, tidak ada informasi jenis yang tersedia tentang jenis Streamitu saat runtime. Semuanya adilStream .

BTW, apa yang salah dengan pendekatan Anda? Terlihat baik untuk saya.

Rohit Jain
sumber
2
@DR Generik dalam C#diimplementasikan menggunakan reifikasi, sedangkan di Jawa, diimplementasikan menggunakan penghapusan. Keduanya diimplementasikan dalam berbagai mode yang mendasarinya. Jadi, Anda tidak dapat mengharapkannya bekerja dengan cara yang sama di kedua bahasa.
Rohit Jain
1
@DR Saya mengerti bahwa penghapusan menimbulkan banyak masalah bagi pemula untuk memahami konsep obat generik di Jawa. Dan karena saya tidak menggunakan C #, saya tidak bisa menjelaskan lebih jauh tentang perbandingan. Tetapi seluruh motivasi di balik penerapan IMO dengan cara ini adalah untuk menghindari perubahan besar dalam implementasi JVM.
Rohit Jain
1
Mengapa "pasti gagal saat runtime"? Seperti yang Anda katakan tidak ada informasi jenis (umum), jadi tidak perlu memeriksa runtime. Mungkin mungkin gagal pada saat runtime, jika jenis yang salah diberi makan melalui, tetapi tidak ada "kepastian" tentang apapun itu.
Hot Licks
1
@RohitJain: Saya tidak mengkritik konsep generik Java, tetapi konsekuensi tunggal ini masih menciptakan kode jelek ;-)
DR
1
@DR - Java generik jelek dari git-go. Sebagian besar hanya fitur C ++ nafsu.
Hot Licks