Misalkan saya memiliki metode yang mengembalikan tampilan baca-saja ke daftar anggota:
class Team {
private List < Player > players = new ArrayList < > ();
// ...
public List < Player > getPlayers() {
return Collections.unmodifiableList(players);
}
}
Lebih lanjut anggap bahwa semua klien lakukan adalah beralih daftar sekali, segera. Mungkin untuk memasukkan pemain ke JList atau sesuatu. Klien tidak menyimpan referensi ke daftar untuk pemeriksaan nanti!
Dengan skenario umum ini, haruskah saya mengembalikan aliran?
public Stream < Player > getPlayers() {
return players.stream();
}
Atau apakah mengembalikan aliran non-idiomatis di Jawa? Apakah aliran dirancang untuk selalu "dihentikan" di dalam ekspresi yang sama dengan yang mereka buat?
java
collections
java-8
encapsulation
java-stream
fredoverflow
sumber
sumber
players.stream()
hanyalah metode yang mengembalikan aliran ke pemanggil. Pertanyaan sebenarnya adalah, apakah Anda benar-benar ingin membatasi penelepon ke satu traversal, dan juga menolaknya akses ke koleksi Anda melaluiCollection
API? Mungkin penelepon hanya ingin keaddAll
koleksi lain?Jawaban:
Jawabannya, seperti biasa, "itu tergantung". Itu tergantung pada seberapa besar koleksi yang dikembalikan. Itu tergantung pada apakah hasilnya berubah dari waktu ke waktu, dan seberapa penting konsistensi dari hasil yang dikembalikan. Dan itu sangat tergantung pada bagaimana pengguna cenderung menggunakan jawabannya.
Pertama, perhatikan bahwa Anda selalu bisa mendapatkan Koleksi dari Stream, dan sebaliknya:
Jadi pertanyaannya adalah, mana yang lebih bermanfaat bagi penelepon Anda.
Jika hasil Anda mungkin tidak terbatas, hanya ada satu pilihan: Streaming.
Jika hasil Anda mungkin sangat besar, Anda mungkin lebih suka Stream, karena mungkin tidak ada nilai apa pun dalam mewujudkannya sekaligus, dan melakukan hal itu dapat menciptakan tekanan tumpukan yang signifikan.
Jika semua yang akan dilakukan pemanggil adalah mengulanginya (pencarian, filter, agregat), Anda harus memilih Stream, karena Stream sudah memiliki bawaan ini dan tidak perlu mematerialisasi koleksi (terutama jika pengguna mungkin tidak memproses seluruh hasil.) Ini adalah kasus yang sangat umum.
Bahkan jika Anda tahu bahwa pengguna akan mengulanginya beberapa kali atau mempertahankannya, Anda masih mungkin ingin mengembalikan Stream sebagai gantinya, karena fakta sederhana bahwa Koleksi apa pun yang Anda pilih untuk dimasukkan ke dalamnya (mis., ArrayList) mungkin bukan formulir yang mereka inginkan, dan kemudian pemanggil harus menyalinnya. jika Anda mengembalikan aliran, mereka dapat melakukannya
collect(toCollection(factory))
dan mendapatkannya dalam bentuk yang mereka inginkan.Kasus "prefer Stream" di atas sebagian besar berasal dari fakta bahwa Stream lebih fleksibel; Anda dapat terlambat mengikat ke bagaimana Anda menggunakannya tanpa menimbulkan biaya dan kendala mewujudkannya ke Koleksi.
Satu-satunya kasus di mana Anda harus mengembalikan Koleksi adalah ketika ada persyaratan konsistensi yang kuat, dan Anda harus menghasilkan snapshot konsisten dari target yang bergerak. Kemudian, Anda akan ingin memasukkan elemen ke dalam koleksi yang tidak akan berubah.
Jadi saya akan mengatakan bahwa sebagian besar waktu, Stream adalah jawaban yang tepat - lebih fleksibel, tidak memaksakan biasanya biaya materialisasi yang tidak perlu, dan dapat dengan mudah diubah menjadi Koleksi pilihan Anda jika diperlukan. Tetapi kadang-kadang, Anda mungkin harus mengembalikan Koleksi (katakanlah, karena persyaratan konsistensi yang kuat), atau Anda mungkin ingin mengembalikan Koleksi karena Anda tahu bagaimana pengguna akan menggunakannya dan tahu ini adalah hal yang paling nyaman bagi mereka.
sumber
Saya punya beberapa poin untuk ditambahkan ke jawaban luar biasa Brian Goetz .
Sangat umum untuk mengembalikan Stream dari panggilan metode gaya "pengambil". Lihat halaman penggunaan Stream di Java 8 javadoc dan cari "metode ... yang mengembalikan Stream" untuk paket selain
java.util.Stream
. Metode ini biasanya pada kelas yang mewakili atau dapat berisi beberapa nilai atau agregasi sesuatu. Dalam kasus seperti itu, API biasanya telah mengembalikan koleksi atau arraynya. Untuk semua alasan yang dicatat Brian dalam jawabannya, sangat fleksibel untuk menambahkan metode Stream-return di sini. Banyak dari kelas-kelas ini sudah memiliki metode collection-atau array-return, karena kelas mendahului API Streams. Jika Anda merancang API baru, dan masuk akal untuk memberikan metode pengembalian-arus, mungkin juga tidak perlu menambahkan metode pengembalian-pengumpulan.Brian menyebutkan biaya "mewujudkan" nilai-nilai menjadi koleksi. Untuk memperkuat poin ini, sebenarnya ada dua biaya di sini: biaya menyimpan nilai dalam koleksi (alokasi memori dan penyalinan) dan juga biaya untuk menciptakan nilai di tempat pertama. Biaya yang terakhir sering dapat dikurangi atau dihindari dengan mengambil keuntungan dari perilaku mencari kemalasan Stream. Contoh yang baik dari ini adalah API di
java.nio.file.Files
:Tidak hanya itu
readAllLines
harus menyimpan seluruh isi file dalam memori untuk menyimpannya ke dalam daftar hasil, tetapi juga harus membaca file sampai akhir sebelum mengembalikan daftar. Thelines
Metode dapat segera setelah itu telah dilakukan beberapa setup kembali, meninggalkan membaca file dan garis breaking sampai nanti ketika itu diperlukan - atau tidak sama sekali. Ini adalah keuntungan besar, jika misalnya, penelepon hanya tertarik pada sepuluh baris pertama:Tentu saja ruang memori yang cukup dapat disimpan jika pemanggil menyaring aliran untuk hanya mengembalikan garis yang cocok dengan pola, dll.
Sebuah idiom yang tampaknya muncul adalah menamai metode aliran-kembali setelah jamak dari nama hal-hal yang diwakilinya atau mengandung, tanpa
get
awalan. Selain itu, walaupunstream()
adalah nama yang masuk akal untuk metode pengembalian aliran ketika hanya ada satu set nilai yang mungkin untuk dikembalikan, kadang-kadang ada kelas yang memiliki agregasi dari beberapa jenis nilai. Misalnya, Anda memiliki beberapa objek yang berisi atribut dan elemen. Anda mungkin menyediakan dua API yang mengembalikan aliran:sumber
Begitulah cara mereka digunakan dalam kebanyakan contoh.
Catatan: mengembalikan Stream tidak jauh berbeda dengan mengembalikan Iterator (diakui dengan kekuatan yang jauh lebih ekspresif)
IMHO solusi terbaik adalah merangkum mengapa Anda melakukan ini, dan tidak mengembalikan koleksi.
misalnya
atau jika Anda bermaksud menghitungnya
sumber
Jika aliran terbatas, dan ada operasi yang diharapkan / normal pada objek yang dikembalikan yang akan melempar pengecualian yang diperiksa, saya selalu mengembalikan Koleksi. Karena jika Anda akan melakukan sesuatu pada masing-masing objek yang dapat melempar pengecualian cek, Anda akan membenci aliran. Satu kekurangan nyata dengan aliran saya ada ketidakmampuan untuk menangani pengecualian diperiksa dengan elegan.
Sekarang, mungkin itu adalah tanda bahwa Anda tidak perlu pengecualian yang diperiksa, yang adil, tetapi kadang-kadang tidak dapat dihindari.
sumber
Berbeda dengan koleksi, stream memiliki karakteristik tambahan . Aliran yang dikembalikan dengan metode apa pun mungkin:
Perbedaan-perbedaan ini juga ada dalam koleksi, tetapi ada mereka bagian dari kontrak yang jelas:
Sebagai konsumen aliran (baik dari pengembalian metode atau sebagai parameter metode) ini adalah situasi yang berbahaya dan membingungkan. Untuk memastikan algoritme mereka berperilaku dengan benar, konsumen aliran perlu memastikan algoritma tidak membuat asumsi yang salah tentang karakteristik aliran. Dan itu adalah hal yang sangat sulit untuk dilakukan. Dalam pengujian unit, itu berarti Anda harus melipatgandakan semua tes Anda untuk diulangi dengan konten aliran yang sama, tetapi dengan aliran yang
Penjaga metode penulisan untuk stream yang melempar IllegalArgumentException jika aliran input memiliki karakteristik melanggar algoritma Anda sulit, karena properti disembunyikan.
Yang meninggalkan Stream hanya sebagai pilihan yang valid dalam metode tanda tangan ketika tidak ada masalah di atas masalah, yang jarang terjadi.
Jauh lebih aman untuk menggunakan tipe data lain dalam tanda tangan metode dengan kontrak eksplisit (dan tanpa melibatkan pemrosesan thread-pool) yang membuatnya tidak mungkin untuk secara tidak sengaja memproses data dengan asumsi yang salah tentang keteraturan, ukuran atau paralelitas (dan penggunaan threadpool).
sumber
Saya pikir itu tergantung pada skenario Anda. Mungkin, jika Anda membuat
Team
implementasi AndaIterable<Player>
, itu sudah cukup.atau dengan gaya fungsional:
Tetapi jika Anda ingin api yang lebih lengkap dan lancar, aliran bisa menjadi solusi yang baik.
sumber
stream.count()
juga cukup mudah), angka itu tidak benar-benar sangat berarti untuk apa pun selain debugging atau memperkirakan.Sementara beberapa responden berprofil tinggi memberikan nasihat umum yang bagus, saya terkejut tidak ada yang menyatakan:
Jika Anda sudah memiliki "terwujud"
Collection
di tangan (yaitu sudah dibuat sebelum panggilan - seperti halnya dalam contoh yang diberikan, di mana itu adalah bidang anggota), tidak ada titik mengubahnya menjadi aStream
. Penelepon dapat dengan mudah melakukannya sendiri. Sedangkan, jika penelepon ingin mengkonsumsi data dalam bentuk aslinya, Anda mengubahnya menjadiStream
memaksa mereka untuk melakukan pekerjaan yang berlebihan untuk mematerialisasi ulang salinan dari struktur asli.sumber
sumber
Saya mungkin akan memiliki 2 metode, satu untuk mengembalikan
Collection
dan satu untuk mengembalikan koleksi sebagaiStream
.Ini adalah yang terbaik dari kedua dunia. Klien dapat memilih jika mereka menginginkan Daftar atau Aliran dan mereka tidak harus melakukan pembuatan objek tambahan untuk membuat salinan daftar yang tidak dapat diubah hanya untuk mendapatkan Aliran.
Ini juga hanya menambahkan 1 metode lagi ke API Anda sehingga Anda tidak memiliki terlalu banyak metode
sumber