Dalam artikel saya sebelumnya, “ Mengalami ImmutableList di Jawa, ” saya mengusulkan solusi untuk masalah tidak adanya daftar yang tidak dapat diubah di Jawa, yang tidak diperbaiki , baik sekarang maupun sebelumnya , di Jawa.
Solusinya kemudian dikerjakan hanya pada tingkat "ada ide seperti itu", dan implementasi dalam kode itu bengkok, karena itu semuanya dianggap agak skeptis. Pada artikel ini, saya mengusulkan solusi yang dimodifikasi. Logika penggunaan dan API dibawa ke tingkat yang dapat diterima. Implementasi dalam kode hingga tingkat beta.
Pernyataan masalah
Kami akan menggunakan definisi dari artikel asli. Secara khusus, ini berarti ImmutableList
adalah daftar referensi yang tidak dapat diubah untuk beberapa objek. Jika objek-objek ini berubah menjadi tidak berubah, maka daftar juga tidak akan menjadi objek abadi, terlepas dari namanya. Dalam praktiknya, ini tidak mungkin menyakiti siapa pun, tetapi untuk menghindari harapan yang tidak dapat dibenarkan perlu disebutkan.
Juga jelas bahwa ketidakmampuan daftar dapat "diretas" melalui refleksi, atau dengan membuat kelas Anda sendiri dalam paket yang sama, diikuti dengan naik ke bidang yang dilindungi daftar, atau sesuatu yang serupa.
Berbeda dengan artikel asli, kami tidak akan berpegang pada prinsip "semua atau tidak sama sekali": penulis di sana tampaknya percaya bahwa jika masalahnya tidak dapat diselesaikan di tingkat JDK, maka tidak ada yang harus dilakukan. (Sebenarnya, pertanyaan lain, "tidak dapat diselesaikan" atau "penulis Java tidak memiliki keinginan untuk menyelesaikannya." Sepertinya saya masih mungkin dengan menambahkan antarmuka, kelas, dan metode tambahan untuk membawa koleksi yang ada lebih dekat ke penampilan yang diinginkan, meskipun kurang cantik daripada jika Anda langsung memikirkannya, tapi sekarang ini bukan tentang itu.)
Kami akan membuat perpustakaan yang berhasil hidup berdampingan dengan koleksi yang ada di Jawa.
Gagasan utama perpustakaan:
- Ada
MutableList
dan MutableList
. Dengan casting type tidak mungkin untuk mendapatkan satu dari yang lain. - Dalam proyek kami, yang ingin kami tingkatkan menggunakan perpustakaan, kami mengganti semua
List
dengan salah satu dari dua antarmuka ini. Jika suatu saat Anda tidak dapat melakukannya tanpa List
, maka pada kesempatan pertama kami akan mengonversi List
dari / ke salah satu dari dua antarmuka. Hal yang sama berlaku untuk saat-saat menerima / mengirimkan data ke perpustakaan pihak ketiga menggunakan List
. - Konversi timbal balik antara
MutableList
, MutableList
, List
harus dilakukan secepat mungkin (yaitu, tanpa menyalin daftar, jika mungkin). Tanpa konversi pulang-pergi "murah", seluruh ide mulai tampak meragukan.
Perlu dicatat bahwa hanya daftar yang dipertimbangkan, karena saat ini hanya diterapkan di perpustakaan. Tapi tidak ada yang mencegah perpustakaan dari melengkapi dengan Set
dan Map
.
API
Daftar Abadi
ImmutableList
adalah penerus dari ReadOnlyList
(yang, seperti pada artikel sebelumnya, adalah antarmuka List
disalin dari mana semua metode bermutasi dilemparkan). Metode ditambahkan:
List<E> toList(); MutableList<E> mutable(); boolean contentEquals(Iterable<? extends E> iterable);
Metode toList
memberikan kemampuan untuk meneruskan ImmutableList
ke potongan kode yang menunggu List
. Wrapper dikembalikan di mana semua metode modifikasi mengembalikan UnsupportedOperationException
, dan metode yang tersisa diarahkan ke ImmutableList
asli.
Metode mutable
mengubah MutableList
menjadi MutableList
. Wrapper dikembalikan di mana semua metode dialihkan ke ImmutableList
asli hingga perubahan pertama. Sebelum perubahan, pembungkus dilepaskan dari ImmutableList
asli, menyalin isinya ke ArrayList
internal, di mana semua operasi kemudian dialihkan.
Metode contentEquals
dimaksudkan untuk membandingkan konten daftar dengan konten yang lulus Iterable
sewenang-wenang (tentu saja, operasi ini hanya bermakna bagi implementasi Iterable
yang memiliki urutan elemen yang berbeda).
Perhatikan bahwa dalam implementasi ReadOnlyList
, metode iterator
dan listIterator
mengembalikan standar java.util.Iterator
/ java.util.ListIterator
. Iterator ini berisi metode modifikasi yang harus ditekan dengan melempar UnsupportedOperationException
. Akan lebih baik untuk membuat ReadOnlyIterator
kami, tetapi dalam hal ini kami tidak dapat menulis for (Object item : immutableList)
, yang akan segera merusak semua kesenangan menggunakan perpustakaan.
MutableList
MutableList
adalah turunan dari List
reguler. Metode ditambahkan:
ImmutableList<E> snapshot(); void releaseSnapshot(); boolean contentEquals(Iterable<? extends E> iterable);
Metode snapshot
dirancang untuk mendapatkan "snapshot" dari keadaan MutableList
saat ini sebagai MutableList
. "Snapshot" disimpan di dalam MutableList
, dan jika keadaan belum berubah pada saat pemanggilan metode selanjutnya, instance ImmutableList
. "Snapshot" yang disimpan di dalamnya dibuang saat pertama kali metode modifikasi dipanggil, atau ketika releaseSnapshot
dipanggil. Metode releaseSnapshot
dapat digunakan untuk menghemat memori jika Anda yakin tidak ada yang akan membutuhkan "snapshot", tetapi metode modifikasi tidak akan segera dipanggil.
Mutabor
Kelas Mutabor
menyediakan seperangkat metode statis yang merupakan "titik masuk" ke perpustakaan.
Ya, proyek ini sekarang disebut "mutabor" (itu sesuai dengan "bisa berubah", dan dalam terjemahan itu berarti "Saya akan mengubah", yang sesuai dengan ide cepat "mengubah" beberapa jenis koleksi menjadi yang lain).
public static <E> ImmutableList<E> copyToImmutableList(E[] original); public static <E> ImmutableList<E> copyToImmutableList(Collection<? extends E> original); public static <E> ImmutableList<E> convertToImmutableList(Collection<? extends E> original); public static <E> MutableList<E> copyToMutableList(Collection<? extends E> original); public static <E> MutableList<E> convertToMutableList(List<E> original);
copyTo*
dirancang untuk membuat koleksi yang sesuai dengan menyalin data yang disediakan. Metode convertTo*
konversi cepat koleksi yang ditransfer ke jenis yang diinginkan, dan jika tidak memungkinkan untuk dengan cepat dikonversi, mereka melakukan penyalinan lambat. Jika konversi cepat berhasil, maka koleksi asli dihapus, dan diasumsikan bahwa itu tidak akan digunakan di masa depan (meskipun bisa, tetapi ini hampir tidak masuk akal).
Panggilan ke konstruktor ImmutableList
implementasi MutableList
/ MutableList
disembunyikan. Diasumsikan bahwa pengguna hanya berurusan dengan antarmuka, ia tidak membuat objek seperti itu, dan menggunakan metode yang dijelaskan di atas untuk mengubah koleksi.
Detail implementasi
ImmutableListImpl
Merangkum berbagai objek. Implementasi kira-kira sesuai dengan implementasi ArrayList
, dari mana semua metode modifikasi dan pemeriksaan untuk modifikasi bersamaan dilemparkan.
Implementasi metode toList
dan contentEquals
juga cukup sepele. Metode toList
mengembalikan pembungkus yang mengalihkan panggilan ke ImmutableList
diberikan; penyalinan data secara lambat tidak terjadi.
Metode mutable
mengembalikan MutableListImpl
dibuat berdasarkan MutableListImpl
ini. Penyalinan data tidak terjadi sampai metode pengubahan apa pun dipanggil pada MutableList
diterima.
MutableListImpl
Meringkas tautan ke List
dan List
ImmutableList
. Saat membuat objek, hanya satu dari dua tautan ini yang selalu diisi, yang lainnya tetap null
.
protected ImmutableList<E> immutable; protected List<E> list;
Metode Immutable mengarahkan ulang panggilan ke ImmutableList
jika bukan null
, dan ke List
sebaliknya.
Metode memodifikasi mengarahkan panggilan ke List
, setelah menginisialisasi:
protected void beforeChange() { if (list == null) { list = new ArrayList<>(immutable.toList()); } immutable = null; }
Metode snapshot
terlihat seperti ini:
public ImmutableList<E> snapshot() { if (immutable != null) { return immutable; } immutable = InternalUtils.convertToImmutableList(list); if (immutable != null) {
Penerapan contentEquals
releaseSnapshot
dan contentEquals
sepele.
Pendekatan ini memungkinkan Anda untuk meminimalkan jumlah salinan data selama penggunaan "biasa", menggantikan salinan dengan konversi cepat.
Konversi daftar cepat
Konversi cepat dimungkinkan untuk kelas ArrayList
atau Arrays$ArrayList
(hasil dari metode Arrays.asList()
). Dalam praktiknya, dalam sebagian besar kasus, justru kelas-kelas inilah yang muncul.
Di dalam kelas-kelas ini berisi array elemen. Inti dari konversi cepat adalah untuk mendapatkan referensi ke array ini melalui refleksi (ini adalah bidang pribadi) dan menggantinya dengan referensi ke array kosong. Ini memastikan bahwa satu-satunya referensi ke array tetap dengan objek kita, dan array tetap tidak berubah.
Di versi perpustakaan sebelumnya, konversi cepat dari jenis koleksi dilakukan dengan memanggil konstruktor. Pada saat yang sama, objek koleksi asli memburuk (menjadi tidak cocok untuk digunakan lebih lanjut), yang tidak Anda sadari secara tidak terduga dari perancang. Sekarang, metode statis khusus digunakan untuk konversi, dan koleksi asli tidak rusak, tetapi hanya dihapus. Dengan demikian, perilaku yang tidak biasa yang menakutkan dihilangkan.
Masalah dengan equals / hashCode
Koleksi Java menggunakan pendekatan yang sangat aneh untuk mengimplementasikan metode equals
dan hashCode
.
Perbandingan dilakukan sesuai dengan konten, yang tampaknya logis, tetapi kelas daftar itu sendiri tidak diperhitungkan. Karenanya, misalnya, ArrayList
dan LinkedList
dengan konten yang sama akan equals
.
Inilah implementasi equals / hashCode dari AbstractList (dari mana ArrayList diwarisi) public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof List)) return false; ListIterator<E> e1 = listIterator(); ListIterator e2 = ((List) o).listIterator(); while (e1.hasNext() && e2.hasNext()) { E o1 = e1.next(); Object o2 = e2.next(); if (!(o1==null ? o2==null : o1.equals(o2))) return false; } return !(e1.hasNext() || e2.hasNext()); } public int hashCode() { int hashCode = 1; for (E e : this) hashCode = 31*hashCode + (e==null ? 0 : e.hashCode()); return hashCode; }
Dengan demikian, sekarang benar-benar semua implementasi List
diharuskan memiliki implementasi yang equals
(dan, sebagai hasilnya, hashCode
). Kalau tidak, Anda bisa mendapatkan situasi ketika a.equals(b) && !b.equals(a)
, yang tidak baik. Situasi serupa terjadi dengan Set
dan Map
.
Dalam aplikasi ke pustaka, ini berarti bahwa implementasi equals
dan hashCode
untuk MutableList
ditentukan sebelumnya, dan dalam implementasi seperti itu, MutableList
dan MutableList
dengan konten yang sama tidak dapat equals
(karena ImmutableList
bukan List
). Oleh karena itu, metode contentEquals
telah ditambahkan untuk membandingkan konten.
Implementasi metode equals
dan hashCode
untuk ImmutableList
dibuat sangat mirip dengan versi dari AbstractList
, tetapi dengan penggantian List
oleh ReadOnlyList
.
Total
Sumber dan tes perpustakaan diposting dengan referensi dalam bentuk proyek pakar.
Jika seseorang ingin menggunakan perpustakaan, ia telah membuat grup untuk "umpan balik".
Menggunakan perpustakaan cukup jelas, berikut adalah contoh singkat:
private boolean myBusinessProcess() { List<Entity> tempFromDb = queryEntitiesFromDatabase("SELECT * FROM my_table"); ImmutableList<Entity> fromDb = Mutabor.convertToImmutableList(tempFromDb); if (fromDb.isEmpty() || !someChecksPassed(fromDb)) { return false; }
Semoga beruntung untuk semuanya! Kirim laporan bug!