Halo semuanya!
Hari ini, perhatian Anda diundang ke terjemahan artikel yang ditulis dengan serius tentang salah satu masalah dasar Jawa - kemampuan berubah-ubah, dan bagaimana hal itu memengaruhi struktur struktur data dan cara bekerja dengannya. Materi diambil dari blog Nicolai Parlog, yang gaya sastra briliannya kami benar-benar berusaha untuk tetap menerjemahkan. Nicholas sendiri sangat dicirikan oleh
kutipan dari blog perusahaan JUG.ru di Habré; mari kita mengutip keseluruhan bagian ini:

Nikolay Parlog adalah dude media yang melakukan review pada fitur Java. Tapi dia bukan dari Oracle pada saat yang sama, jadi ulasannya sangat jujur dan mudah dimengerti. Terkadang setelah mereka seseorang dipecat, tetapi jarang. Nikolay akan berbicara tentang masa depan Jawa, apa yang akan ada di versi baru. Dia pandai berbicara tentang tren dan umumnya tentang dunia besar. Dia adalah teman yang sangat baik membaca dan terpelajar. Bahkan laporan sederhana menyenangkan untuk didengarkan, setiap saat Anda mempelajari sesuatu yang baru. Selain itu, Nicholas tahu melampaui apa yang dia katakan. Artinya, Anda dapat datang ke laporan apa pun dan hanya menikmatinya, bahkan jika ini bukan topik Anda sama sekali. Dia sedang mengajar. Dia menulis "The Java Module System" untuk Manning Publishing House, mengelola blog tentang pengembangan perangkat lunak di codefx.org, dan telah lama terlibat dalam beberapa proyek sumber terbuka. Dia dapat dipekerjakan tepat di konferensi, dia adalah pekerja lepas. Benar, freelancer yang sangat mahal. Inilah laporannya .
Kami membaca dan memilih. Yang terutama menyukai posting - kami juga menyarankan Anda melihat komentar pembaca pada posting asli.
Volatilitas itu buruk, bukan? Dengan demikian,
ketetapan adalah baik . Struktur data utama di mana imutabilitas sangat bermanfaat adalah koleksi: di Jawa itu adalah daftar (
List
), satu set (
Set
) dan kamus (
Map
). Namun, meskipun JDK hadir dengan Koleksi yang tidak dapat diubah (atau tidak dapat dimodifikasi?), Sistem tipe tidak tahu apa-apa tentang ini. Tidak ada
ImmutableList
di JDK, dan jenis ini dari Guava tampaknya sama sekali tidak berguna bagi saya. Tapi mengapa? Mengapa tidak menambahkan
Immutable...
ke dalam campuran ini dan tidak mengatakan bahwa itu seharusnya?
Apa itu koleksi abadi?
Dalam terminologi JDK, makna kata "
tidak berubah " dan "
tidak dapat dimodifikasi " telah berubah selama beberapa tahun terakhir. Awalnya, "tidak dapat dimodifikasi" disebut sebuah instance yang tidak memungkinkan mutabilitas (mutabilitas): sebagai tanggapan terhadap metode yang berubah, ia melemparkan
UnsupportedOperationException
. Namun, itu bisa diubah dengan cara lain - mungkin karena itu hanya pembungkus koleksi yang bisa berubah. Pandangan ini tercermin dalam metode
Collections::unmodifiableList
,
unmodifiableSet
dan
unmodifiableMap
, serta di
JavaDoc mereka .
Awalnya, istilah "
tidak berubah " mengacu pada koleksi yang dikembalikan oleh
metode pabrik dari koleksi Java 9 . Koleksi itu sendiri tidak dapat diubah dengan cara apa pun (ya, ada
refleksi , tetapi tidak masuk hitungan), oleh karena itu, tampaknya mereka membenarkan nama mereka. Sayangnya, kebingungan sering muncul karena ini. Misalkan ada metode yang menampilkan semua elemen dari koleksi abadi di layar - akankah ia selalu memberikan hasil yang sama? Hah? Atau tidak?
Jika Anda tidak segera menjawab
tidak , itu berarti bahwa Anda baru saja melihat secara persis kebingungan apa yang mungkin terjadi di sini. "
Koleksi Agen Rahasia yang Tidak Berubah " - tampaknya terdengar sangat mirip dengan "
koleksi Agen Rahasia yang tidak dapat diubah, " tetapi kedua entitas ini mungkin tidak identik. Koleksi yang tidak dapat diubah tidak dapat diedit menggunakan operasi sisipkan / hapus / bersihkan, dll., Tetapi jika agen rahasia dapat berubah (meskipun karakter karakter dalam film mata-mata sangat buruk sehingga mereka tidak benar-benar mempercayainya), maka itu tidak berarti bahwa seluruh koleksi agen rahasia tidak dapat diubah. Oleh karena itu, sekarang ada pergeseran ke arah penamaan koleksi yang
tidak dapat dimodifikasi , daripada
tidak berubah , yang diabadikan dalam
edisi baru JavaDoc .
Koleksi abadi yang dibahas dalam artikel ini mungkin mengandung elemen yang bisa berubah.
Secara pribadi, saya tidak suka revisi terminologi seperti itu. Menurut pendapat saya, istilah "koleksi tidak dapat diubah" seharusnya hanya berarti bahwa koleksi itu sendiri tidak dapat diubah, tetapi tidak boleh mengkarakterisasi elemen-elemen yang terkandung di dalamnya. Dalam hal ini, ada satu hal positif lagi: istilah "ketidakberdayaan" dalam ekosistem Jawa tidak berubah menjadi omong kosong.
Dengan satu atau lain cara, dalam artikel ini kita akan berbicara tentang
koleksi abadi , di mana ...
- Contoh-contoh yang terkandung dalam koleksi ditentukan pada tahap desain
- Salinan ini - jumlah yang genap, tidak mengurangi atau menambah
- Tidak ada pernyataan yang dibuat sehubungan dengan mutabilitas elemen-elemen ini.
Mari kita memikirkan fakta bahwa sekarang kita akan berlatih menambahkan koleksi abadi. Tepatnya - daftar abadi. Segala sesuatu yang akan dikatakan tentang daftar sama-sama berlaku untuk koleksi jenis lain.
Mulai menambahkan koleksi abadi!
Mari kita membuat antarmuka
ImmutableList
dan membuatnya, relatif terhadap
List
, eh ... apa? Supertype atau subtype? Mari kita memikirkan opsi pertama.

Indahnya,
ImmutableList
tidak
ImmutableList
metode yang
ImmutableList
, jadi menggunakannya selalu aman, bukan? Jadi? Tidak, tuan.
List<Agent> agents = new ArrayList<>();
Contoh ini menunjukkan bahwa dimungkinkan untuk mentransfer daftar yang tidak sepenuhnya tidak dapat diubah tersebut ke API, yang operasinya dapat dibangun di atas ketidakmampuan dan, karenanya, membatalkan semua jaminan yang mungkin disiratkan oleh nama jenis ini. Inilah resep untuk Anda yang bisa mengakibatkan bencana.
OK, kemudian
ImmutableList
memperpanjang
List
. Mungkin?

Sekarang, jika API mengharapkan daftar yang tidak dapat diubah, maka ia akan menerima daftar seperti itu, tetapi ada dua kelemahan:
- Daftar yang tidak dapat diubah harus tetap menawarkan metode modifikasi (seperti yang didefinisikan dalam supertype), dan satu-satunya implementasi yang mungkin akan menimbulkan pengecualian
- Instance
ImmutableList
juga instance Instances, dan ketika ditugaskan ke variabel seperti itu, diteruskan sebagai argumen seperti itu, atau dikembalikan dari jenis ini, logis untuk mengasumsikan bahwa mutabilitas diperbolehkan.
Dengan demikian, ternyata Anda hanya dapat menggunakan
ImmutableList
secara lokal, karena ia melewati batas API sebagai
List
, yang mengharuskan Anda untuk melakukan tindakan pencegahan tingkat manusia super, atau meledak saat runtime. Ini tidak seburuk
List
memperpanjang
ImmutableList
, tetapi solusi seperti itu masih jauh dari ideal.
Ini persis apa yang ada dalam pikiran saya ketika saya mengatakan bahwa tipe
ImmutableList
dari Guava praktis tidak berguna. Ini adalah contoh kode yang hebat, sangat andal ketika bekerja dengan daftar lokal yang tidak dapat diubah (itulah sebabnya saya menggunakannya secara aktif), tetapi, dengan menggunakan itu, sangat mudah untuk melampaui benteng yang tidak dapat ditembus, kompilasi yang dijamin, dinding-dindingnya terdiri dari tipe yang tidak dapat diubah - dan hanya di dalamnya tipe abadi dapat sepenuhnya mengungkapkan potensi mereka. Ini lebih baik daripada tidak sama sekali, tetapi tidak efisien sebagai solusi JDK.
Jika
ImmutableList
tidak dapat memperpanjang
List
, dan solusi masih tidak berfungsi, lalu bagaimana cara membuatnya semua berfungsi?
Kekekalan adalah fitur
Masalah yang kami temui dalam dua upaya pertama untuk menambahkan tipe yang tidak dapat diubah adalah kesalahpahaman kami bahwa ketidakmampuan hanyalah tidak adanya sesuatu: kami mengambil
List
, menghapus kode yang berubah darinya, kami mendapatkan
ImmutableList
. Tetapi, pada kenyataannya, semua ini tidak bekerja seperti itu.
Jika kami hanya menghapus metode modifikasi dari
List
, kami mendapatkan daftar hanya-baca. Atau, mengikuti terminologi yang dirumuskan di atas, itu bisa disebut
UnmodifiableList
- itu masih bisa berubah, hanya saja Anda tidak akan mengubahnya.
Sekarang kita dapat menambahkan dua hal lagi ke gambar ini:
- Kita dapat membuatnya bisa berubah dengan menambahkan metode yang sesuai
- Kami dapat membuatnya tidak berubah dengan menambahkan jaminan yang sesuai.
Kekekalan bukanlah tidak adanya mutabilitas, tetapi jaminan bahwa tidak akan ada perubahan
Dalam hal ini, penting untuk dipahami bahwa dalam kedua kasus ini kita berbicara tentang fitur lengkap - ketidakmampuan bukanlah tidak adanya perubahan, tetapi jaminan bahwa tidak akan ada perubahan. Fitur yang ada mungkin tidak selalu menjadi sesuatu yang digunakan untuk kebaikan, itu juga dapat menjamin bahwa sesuatu yang buruk tidak akan terjadi dalam kode - dalam hal ini, pikirkan, misalnya, tentang keamanan utas.
Jelaslah, ketidakmampuan dan ketidakmampuan bertentangan satu sama lain, dan oleh karena itu kita tidak dapat secara bersamaan menggunakan dua hierarki warisan yang disebutkan di atas. Jenis mewarisi kemampuan dari jenis lain, jadi tidak masalah bagaimana Anda memotongnya, jika salah satu jenis mewarisi dari yang lain, itu akan berisi kedua fitur.
ImmutableList
,
List
dan
ImmutableList
tidak dapat saling memperpanjang. Tetapi kami dibawa ke sini dengan bekerja dengan
UnmodifiableList
, dan ternyata kedua jenis memiliki API yang sama, hanya-baca, yang berarti mereka harus memperluasnya.

Meskipun, saya tidak akan menyebut nama-nama ini dengan tepat, hierarki semacam ini masuk akal. Di Scala, misalnya, ini
praktis dilakukan . Perbedaannya adalah bahwa supertype bersama, yang kami sebut
UnmodifiableList
, mendefinisikan metode yang bisa berubah yang mengembalikan koleksi yang dimodifikasi dan membiarkan yang asli tidak tersentuh. Dengan demikian, daftar yang tidak dapat diubah ternyata bersifat
persisten dan memberikan varian yang dapat berubah dua set metode modifikasi - diwarisi untuk menerima salinan yang dimodifikasi dan miliknya sendiri untuk perubahan yang terjadi.
Bagaimana dengan Jawa? Apakah mungkin memodernisasi hierarki dengan menambahkan supertipe dan saudara kandung yang baru ke dalamnya?
Apakah mungkin untuk meningkatkan koleksi yang tidak dapat dimodifikasi dan tidak dapat diubah?
Tentu saja, tidak ada masalah dalam menambahkan tipe
UnmodifiableList
dan
ImmutableList
dan membuat hierarki warisan yang dijelaskan di atas. Masalahnya adalah bahwa dalam jangka pendek dan menengah praktis tidak akan berguna. Biarkan saya jelaskan.
Yang
ImmutableList
memiliki
UnmodifiableList
,
UnmodifiableList
, dan
List
sebagai tipe - dalam hal ini, API akan dapat dengan jelas menyatakan apa yang mereka butuhkan dan apa yang mereka tawarkan.
public void payAgents(UnmodifiableList<Agent> agents) {
Namun, kecuali jika Anda memulai proyek dari awal, Anda mungkin akan memiliki fungsi seperti itu, dan itu akan terlihat seperti ini:
Ini tidak baik, karena agar koleksi baru yang baru saja diperkenalkan oleh kami bermanfaat, kita perlu bekerja dengannya! (Fiuh). Di atas mengingatkan pada kode aplikasi, oleh karena itu, refactoring
ImmutableList
UnmodifiableList
dan
ImmutableList
, dan Anda dapat mengimplementasikannya, seperti yang ditunjukkan dalam daftar di atas. Ini bisa menjadi potongan besar pekerjaan, ditambah dengan kebingungan ketika Anda perlu mengatur interaksi kode lama dan diperbarui, tetapi setidaknya tampaknya layak.
Bagaimana dengan kerangka kerja, perpustakaan, dan JDK itu sendiri? Semuanya di sini tampak suram. Upaya untuk mengubah parameter atau jenis kembali dari
List
ke
ImmutableList
akan menyebabkan
ketidakcocokan dengan kode sumber , mis. kode sumber yang ada tidak akan dikompilasi dengan versi baru, karena jenis ini tidak saling terkait. Demikian pula, mengubah tipe pengembalian dari
List
ke supertype baru UnmodifiableList akan menghasilkan kesalahan kompilasi.
Dengan diperkenalkannya tipe baru, akan perlu untuk membuat perubahan dan mengkompilasi ulang seluruh ekosistem.
Namun, bahkan jika kita memperluas tipe parameter dari
List
ke
UnmodifiableList
, kita akan mengalami masalah, karena perubahan seperti itu menyebabkan
ketidakcocokan pada level bytecode . Ketika kode sumber memanggil suatu metode, kompiler mengubah panggilan ini menjadi bytecode yang mereferensikan metode target dengan:
- Nama kelas yang instance targetnya dideklarasikan
- Nama metode
- Metode tipe parameter
- Jenis metode pengembalian
Setiap perubahan dalam parameter atau tipe pengembalian metode akan menyebabkan bytecode menunjukkan tanda tangan yang salah ketika merujuk ke metode; sebagai hasilnya, kesalahan
NoSuchMethodError
akan terjadi selama eksekusi. Jika perubahan yang Anda lakukan kompatibel dengan kode sumber - misalnya, jika Anda berbicara tentang mempersempit tipe pengembalian atau memperluas jenis parameter - maka kompilasi ulang harus memadai. Namun, dengan perubahan yang jauh jangkauannya, misalnya, dengan diperkenalkannya koleksi baru, semuanya tidak begitu sederhana: untuk mengkonsolidasikan perubahan tersebut, Anda perlu mengkompilasi ulang seluruh ekosistem Jawa. Ini bisnis yang hilang.
Satu-satunya cara yang mungkin untuk mengambil keuntungan dari koleksi baru seperti itu tanpa merusak kompatibilitas adalah dengan menduplikasi setiap metode yang ada dengan nama baru, mengubah API, dan kemudian menandai versi lama sebagai yang tidak diinginkan. Bisakah Anda bayangkan betapa monumental dan hampir tak terbatas tugas seperti itu?!
Refleksi
Tentu saja, tipe koleksi yang tidak berubah adalah hal hebat yang saya ingin miliki, tetapi kita tidak mungkin melihat sesuatu seperti ini di JDK. Implementasi kompeten
List
dan
ImmutableList
tidak
ImmutableList
pernah
ImmutableList
saling memperluas (pada kenyataannya, keduanya memperpanjang tipe daftar yang sama dengan
UnmodifiableList
, yang hanya-baca), yang membuatnya sulit untuk mengintegrasikan tipe-tipe tersebut ke dalam API yang ada.
Di luar konteks hubungan spesifik antara jenis, mengubah tanda tangan metode yang ada selalu penuh dengan masalah, karena perubahan tersebut tidak kompatibel dengan bytecode. Ketika mereka diperkenalkan, setidaknya diperlukan kompilasi ulang, dan ini adalah perubahan yang menghancurkan yang akan mempengaruhi seluruh ekosistem Jawa.
Karena itu, saya percaya bahwa hal seperti ini tidak akan terjadi - tidak pernah dan tidak akan pernah.