Tim Java telah melakukan banyak pekerjaan untuk menghilangkan hambatan pemrograman fungsional di Jawa 8. Secara khusus, perubahan pada java.util Collections melakukan pekerjaan yang bagus untuk mengubah transformasi menjadi operasi yang mengalir sangat cepat. Mengingat betapa baiknya pekerjaan yang telah mereka lakukan dengan menambahkan fungsi kelas satu dan metode fungsional pada koleksi, mengapa mereka benar-benar gagal untuk menyediakan koleksi yang tidak dapat diubah atau bahkan antarmuka koleksi yang tidak dapat diubah?
Tanpa mengubah kode apa pun yang ada, tim Java dapat kapan saja menambahkan antarmuka tidak berubah yang sama dengan yang bisa diubah, minus metode "set" dan membuat antarmuka yang ada diperluas darinya, seperti ini:
ImmutableIterable
____________/ |
/ |
Iterable ImmutableCollection
| _______/ / \ \___________
| / / \ \
Collection ImmutableList ImmutableSet ImmutableMap ...
\ \ \_________|______________|__________ |
\ \___________|____________ | \ |
\___________ | \ | \ |
List Set Map ...
Tentu, operasi seperti List.add () dan Map.put () saat ini mengembalikan nilai boolean atau sebelumnya untuk kunci yang diberikan untuk menunjukkan apakah operasi berhasil atau gagal. Koleksi yang tidak dapat diubah harus memperlakukan metode seperti pabrik dan mengembalikan koleksi baru yang mengandung elemen yang ditambahkan - yang tidak sesuai dengan tanda tangan saat ini. Tapi itu bisa diatasi dengan menggunakan nama metode yang berbeda seperti ImmutableList.append () atau .addAt () dan ImmutableMap.putEntry (). Verbositas yang dihasilkan akan lebih besar daripada manfaat dari bekerja dengan koleksi yang tidak dapat diubah, dan sistem tipe akan mencegah kesalahan memanggil metode yang salah. Seiring waktu, metode lama bisa ditinggalkan.
Menangkan koleksi abadi:
- Kesederhanaan - alasan tentang kode lebih sederhana ketika data yang mendasarinya tidak berubah.
- Dokumentasi - jika suatu metode mengambil antarmuka koleksi yang tidak dapat diubah, Anda tahu itu tidak akan mengubah koleksi itu. Jika suatu metode mengembalikan koleksi yang tidak dapat diubah, Anda tahu Anda tidak bisa memodifikasinya.
- Concurrency - koleksi yang tidak dapat diubah dapat dibagikan secara aman di seluruh utas.
Sebagai seseorang yang telah mencicipi bahasa yang menganggap tidak dapat diubah, sangat sulit untuk kembali ke Wild West dari mutasi yang merajalela. Koleksi Clojure (urutan abstraksi) sudah memiliki semua yang disediakan oleh koleksi Java 8, plus ketidakmampuan (meskipun mungkin menggunakan memori dan waktu ekstra karena daftar tautan yang disinkronkan alih-alih streaming). Scala memiliki koleksi yang bisa berubah dan tidak dapat diubah dengan satu set operasi penuh, dan meskipun operasi-operasi itu bersemangat, memanggil .iterator memberikan pandangan malas (dan ada cara lain untuk mengevaluasi mereka dengan malas). Saya tidak melihat bagaimana Java dapat terus bersaing tanpa koleksi abadi.
Adakah yang bisa mengarahkan saya ke sejarah atau diskusi tentang ini? Tentunya itu adalah tempat umum.
sumber
const
koleksiCollections.unmodifiable*()
untuk apa. tapi jangan memperlakukan ini sebagai tidak berubah ketika tidakImmutableList
dalam diagram itu, orang bisa lewat dalam bisa berubahList
? Tidak, itu pelanggaran LSP yang sangat buruk.Jawaban:
Karena koleksi abadi benar-benar membutuhkan berbagi agar dapat digunakan. Kalau tidak, setiap operasi menjatuhkan seluruh daftar ke tumpukan. Bahasa yang sepenuhnya tidak berubah, seperti Haskell, menghasilkan jumlah sampah yang mencengangkan tanpa optimalisasi dan pembagian yang agresif. Memiliki koleksi yang hanya dapat digunakan dengan <50 elemen tidak layak dimasukkan ke perpustakaan standar.
Lebih jauh lagi, koleksi yang tidak dapat diubah seringkali memiliki implementasi yang berbeda secara fundamental dibandingkan dengan koleksi yang dapat berubah. Pertimbangkan misalnya
ArrayList
, kekekalan yang efisien tidakArrayList
akan menjadi array sama sekali! Ini harus diimplementasikan dengan pohon seimbang dengan faktor percabangan besar, Clojure menggunakan 32 IIRC. Membuat koleksi yang dapat diubah menjadi "tidak berubah" dengan hanya menambahkan pembaruan fungsional adalah bug kinerja seperti halnya kebocoran memori.Selain itu, berbagi tidak dapat dilakukan di Jawa. Java menyediakan terlalu banyak pengait yang tidak dibatasi untuk berubah-ubah dan merujuk kesetaraan untuk menjadikan berbagi "hanya pengoptimalan". Mungkin akan sedikit mengganggu Anda jika Anda bisa memodifikasi elemen dalam daftar, dan menyadari Anda baru saja memodifikasi elemen di 20 versi lain dari daftar yang Anda miliki.
Ini juga mengesampingkan kelas besar optimasi yang sangat vital untuk kekekalan yang efisien, berbagi, aliran fusi, apa saja, mutabilitas menghancurkannya. (Itu akan membuat slogan yang bagus untuk penginjil FP)
sumber
String
dalamStringBuffer
, memanipulasi, dan kemudian menyalin data ke dalam kekekalan baruString
. Menggunakan pola seperti itu dengan set dan daftar mungkin sama baiknya dengan menggunakan tipe yang tidak dapat diubah yang dirancang untuk memfasilitasi produksi instance yang sedikit berubah, tetapi masih bisa lebih baik ...Koleksi yang tidak dapat diubah bukan subtipe dari koleksi yang tidak dapat diubah. Sebaliknya, koleksi yang bisa berubah dan tidak berubah adalah saudara kandung dari koleksi yang dapat dibaca. Sayangnya, konsep "readable", "read-only", dan "immutable" nampaknya menjadi kabur bersama, meskipun mereka memiliki tiga arti berbeda.
Kelas basis koleksi atau jenis antarmuka yang dapat dibaca menjanjikan bahwa orang dapat membaca item, dan tidak menyediakan sarana langsung untuk mengubah koleksi, tetapi tidak menjamin bahwa kode yang menerima referensi tidak dapat membuang atau memanipulasinya sedemikian rupa sehingga memungkinkan modifikasi.
Antarmuka koleksi read-only tidak termasuk anggota baru, tetapi hanya boleh dilaksanakan oleh kelas yang menjanjikan bahwa tidak ada cara untuk memanipulasi referensi untuk itu sedemikian rupa untuk mengubah koleksi atau menerima referensi untuk sesuatu itu bisa dilakukan. Namun, itu tidak menjanjikan bahwa koleksi tidak akan dimodifikasi oleh sesuatu yang lain yang memiliki referensi ke internal. Perhatikan bahwa antarmuka kumpulan read-only mungkin tidak dapat mencegah implementasi dengan kelas yang bisa diubah, tetapi dapat menentukan bahwa setiap implementasi, atau kelas yang berasal dari suatu implementasi, yang memungkinkan mutasi akan dianggap sebagai implementasi "tidak sah" atau turunan dari suatu implementasi .
Koleksi yang tidak berubah adalah koleksi yang akan selalu memiliki data yang sama selama referensi apa pun ada. Setiap implementasi antarmuka yang tidak dapat diubah yang tidak selalu mengembalikan data yang sama dalam menanggapi permintaan tertentu rusak.
Hal ini kadang-kadang berguna untuk memiliki sangat terkait jenis bisa berubah dan abadi koleksi yang baik melaksanakan atau berasal dari jenis yang sama "dibaca", dan memiliki tipe dibaca meliputi
AsImmutable
,AsMutable
, danAsNewMutable
metode. Desain semacam itu memungkinkan kode yang ingin menyimpan data dalam koleksi untuk dipanggilAsImmutable
; metode itu akan membuat salinan defensif jika koleksi dapat diubah, tetapi lewati salinan jika sudah tidak berubah.sumber
int
, dengan metodegetLength()
dangetItemAt(int)
.ReadableIndexedIntSequence
, seseorang dapat menghasilkan sebuah instance dari tipe yang tidak dapat diubah dengan array dengan menyalin semua item ke dalam sebuah array, tetapi anggaplah bahwa implementasi tertentu hanya mengembalikan panjang 16777216((long)index*index)>>24
untuk setiap item. Itu akan menjadi urutan bilangan bulat yang sah dan sah, tetapi menyalinnya ke sebuah array akan menghabiskan banyak waktu dan memori.asImmutable
pada instance dari urutan yang ditentukan di atas versus biaya membangun salinan yang didukung oleh array yang tidak dapat diubah]. Saya berpendapat bahwa memiliki antarmuka yang ditentukan untuk tujuan seperti itu mungkin lebih baik daripada mencoba menggunakan pendekatan ad-hoc; IMHO, alasan terbesar ...Java Collections Framework menyediakan kemampuan untuk membuat versi read-only dari koleksi dengan menggunakan enam metode statis di kelas java.util.Collections :
Seperti yang seseorang tunjukkan dalam komentar pada pertanyaan awal, koleksi yang dikembalikan mungkin tidak dianggap tidak dapat diubah karena meskipun koleksi tidak dapat dimodifikasi (tidak ada anggota yang dapat ditambahkan atau dihapus dari koleksi semacam itu), objek yang sebenarnya dirujuk oleh koleksi dapat dimodifikasi jika jenis objeknya memungkinkan.
Namun, masalah ini akan tetap terlepas dari apakah kode mengembalikan satu objek, atau kumpulan objek yang tidak dapat dimodifikasi. Jika tipe memungkinkan objeknya dimutasi, maka keputusan itu dibuat dalam desain tipe dan saya tidak melihat bagaimana perubahan pada JCF dapat mengubah itu. Jika ketetapan penting, maka anggota koleksi harus dari tipe yang tidak dapat diubah.
sumber
immutableList
dll. Metode pabrik yang akan mengembalikan pembungkus baca-saja di sekitar salinan izin masuk daftar kecuali daftar yang lewat sudah tidak dapat diubah . Akan mudah untuk membuat tipe yang ditentukan pengguna seperti itu tetapi untuk satu masalah: tidak akan ada cara bagijoesCollections.immutableList
metode untuk mengenali bahwa itu tidak perlu menyalin objek yang dikembalikan olehfredsCollections.immutableList
.Ini pertanyaan yang sangat bagus. Saya senang menghibur ide bahwa dari semua kode yang ditulis dalam java dan berjalan di jutaan komputer di seluruh dunia, setiap hari, sepanjang waktu, sekitar setengah dari total siklus jam harus dihabiskan dengan melakukan apa pun selain membuat salinan keamanan koleksi yang dikembalikan oleh fungsi. (Dan pengumpulan sampah ini milidetik setelah pembuatannya.)
Persentase programmer java menyadari keberadaan
unmodifiableCollection()
keluarga metodeCollections
kelas, tetapi bahkan di antara mereka, banyak yang tidak peduli dengan itu.Dan saya tidak bisa menyalahkan mereka: sebuah antarmuka yang berpura-pura baca-tulis tetapi akan melempar
UnsupportedOperationException
jika Anda membuat kesalahan dengan menggunakan salah satu metode 'tulis' itu adalah hal yang sangat jahat untuk dimiliki!Sekarang, antarmuka seperti
Collection
yang akan hilangadd()
,remove()
danclear()
metode tidak akan menjadi antarmuka "ImmutableCollection"; itu akan menjadi antarmuka "UnmodifiableCollection". Faktanya, tidak pernah ada antarmuka "ImmutableCollection", karena ketidakmampuan adalah sifat implementasi, bukan karakteristik antarmuka. Saya tahu, itu tidak terlalu jelas; coba saya jelaskan.Misalkan seseorang memberi Anda antarmuka koleksi hanya-baca; apakah aman untuk meneruskannya ke utas lain? Jika Anda tahu pasti bahwa itu merupakan koleksi yang benar-benar abadi, maka jawabannya adalah "ya"; Sayangnya, karena ini adalah antarmuka, Anda tidak tahu bagaimana penerapannya, jadi jawabannya haruslah tidak : untuk semua yang Anda tahu, itu mungkin tampilan yang tidak dapat diubah (untuk Anda) dari koleksi yang sebenarnya bisa diubah, (seperti apa yang Anda dapatkan
Collections.unmodifiableCollection()
,) jadi mencoba membaca darinya sementara utas lain memodifikasinya akan menghasilkan pembacaan data yang korup.Jadi, apa yang telah Anda jelaskan pada dasarnya adalah sekumpulan antarmuka pengumpulan yang tidak "Tidak Berubah", tetapi "Tidak Dapat Diubah". Penting untuk dipahami bahwa "Tidak Dapat Diubah" hanya berarti bahwa siapa pun yang memiliki referensi ke antarmuka seperti itu dicegah dari memodifikasi koleksi yang mendasarinya, dan mereka dicegah hanya karena antarmuka tidak memiliki metode modifikasi apa pun, bukan karena koleksi yang mendasarinya tidak dapat diubah. Koleksi yang mendasarinya mungkin bisa berubah; Anda tidak memiliki pengetahuan tentang, dan tidak memiliki kendali atas itu.
Untuk memiliki koleksi yang tidak berubah, mereka harus berupa kelas , bukan antarmuka!
Kelas-kelas koleksi yang tidak berubah ini harus final, sehingga ketika Anda diberikan referensi ke koleksi tersebut, Anda tahu pasti bahwa itu akan berperilaku sebagai koleksi yang tidak berubah, apa pun yang Anda, atau siapa pun yang memiliki referensi untuk itu, mungkin lakukan dengan itu.
Jadi, dalam rangka untuk memiliki lengkap set koleksi di java, (atau bahasa imperatif deklaratif lainnya,) kita akan memerlukan berikut ini:
Satu set antarmuka koleksi yang tidak dapat dimodifikasi .
Seperangkat antarmuka koleksi yang bisa berubah , memperluas yang tidak dapat dimodifikasi.
Seperangkat kelas koleksi yang dapat diubah yang mengimplementasikan antarmuka yang dapat diubah, dan dengan ekstensi juga antarmuka yang tidak dapat dimodifikasi.
Seperangkat kelas koleksi yang tidak dapat diubah , mengimplementasikan antarmuka yang tidak dapat dimodifikasi, tetapi sebagian besar diedarkan sebagai kelas, untuk menjamin keabadian.
Saya telah menerapkan semua hal di atas untuk bersenang-senang, dan saya menggunakannya dalam proyek, dan mereka bekerja seperti pesona.
Alasan mengapa mereka bukan bagian dari runtime java mungkin karena dianggap bahwa ini akan terlalu banyak / terlalu rumit / terlalu sulit untuk dipahami.
Secara pribadi, saya pikir apa yang saya jelaskan di atas tidak cukup; Satu hal lagi yang tampaknya diperlukan adalah seperangkat antarmuka & kelas yang bisa berubah-ubah untuk kekekalan struktural . (Yang mungkin disebut "Kaku" karena awalan "StructurallyImmutable" terlalu panjang.)
sumber
List<T> add(T t)
- semua metode "mutator" harus mengembalikan koleksi baru yang mencerminkan perubahan. 2. Baik atau buruk, Antarmuka sering mewakili kontrak di samping tanda tangan. Serializable adalah salah satu antarmuka tersebut. Demikian pula, Sebanding mensyaratkan Anda menerapkancompareTo()
metode Anda dengan benar untuk bekerja dengan benar dan idealnya kompatibel denganequals()
danhashCode()
.add()
. Tapi saya kira jika metode mutator ditambahkan ke kelas yang tidak dapat diubah, maka mereka perlu mengembalikan kelas yang juga tidak dapat diubah. Jadi, jika ada masalah mengintai di sana, saya tidak melihatnya.Suppose someone hands you such a read-only collection interface; is it safe to pass it to another thread?
Misalkan seseorang memberi Anda contoh antarmuka koleksi yang bisa diubah. Apakah aman untuk memanggil metode apa pun di dalamnya? Anda tidak tahu bahwa implementasinya tidak berulang selamanya, melempar pengecualian, atau sepenuhnya mengabaikan kontrak antarmuka. Mengapa memiliki standar ganda khusus untuk koleksi tidak berubah?SortedSet
dengan mensubklasifikasikan set dengan implementasi yang tidak sesuai. Atau dengan melewati yang tidak konsistenComparable
. Hampir semuanya bisa rusak jika Anda mau. Saya kira, itulah yang dimaksud @Doval dengan "standar ganda".Koleksi yang tidak dapat diubah dapat menjadi sangat rekursif, dibandingkan satu sama lain, dan tidak efisien tanpa alasan jika kesetaraan objek adalah dengan secureHash. Ini disebut hutan merkle. Itu bisa per koleksi atau di dalam bagian-bagiannya seperti pohon AVL (penyeimbang sendiri) untuk peta yang diurutkan.
Kecuali semua objek java dalam koleksi ini memiliki id unik atau bitstring untuk hash, koleksi tidak memiliki hash untuk secara unik nama itu sendiri.
Contoh: Pada laptop 4x1.6ghz saya, saya dapat menjalankan 200K sha256s per detik dari ukuran terkecil yang cocok dalam 1 siklus hash (hingga 55 byte), dibandingkan dengan op 500k HashMap atau ops 3M dalam hashtable long rs. 200K / log (collectionSize) koleksi baru per detik cukup cepat untuk beberapa hal di mana integritas data dan skalabilitas global anonim penting.
sumber
Performa. Koleksi berdasarkan sifatnya bisa sangat besar. Menyalin 1000 elemen ke struktur baru dengan 1001 elemen alih-alih memasukkan elemen tunggal benar-benar mengerikan.
Konkurensi. Jika Anda memiliki beberapa utas yang berjalan, mereka mungkin ingin mendapatkan versi koleksi saat ini dan bukan versi yang disahkan 12 jam yang lalu ketika utas dimulai.
Penyimpanan. Dengan objek yang tidak dapat diubah di lingkungan multi-utas, Anda dapat berakhir dengan puluhan salinan objek "yang sama" di berbagai titik siklus hidupnya. Tidak masalah untuk Kalender atau objek Tanggal tetapi ketika koleksi 10.000 widget ini akan membunuh Anda.
sumber