Kebetulan secara historis bahwa di Jawa untuk properti objek (properti) tidak ada entitas fisik yang disediakan. Properti di Jawa adalah beberapa konvensi dalam bidang penamaan dan metode akses (accessor) untuk mereka. Dan, meskipun kehadiran sifat-sifat fisik dalam bahasa tersebut akan menyederhanakan banyak kasus (mulai dari generasi pengambil-bodoh yang konyol), tampaknya dalam waktu dekat situasi di Jawa tidak akan berubah.
Namun demikian, ketika mengembangkan aplikasi bisnis multilayer dan menggunakan berbagai kerangka kerja untuk memetakan dan mengikat data, sering kali perlu untuk memberikan referensi ke properti objek. Mari kita pertimbangkan opsi apa untuk ini.
Gunakan nama properti
Sejauh ini, satu-satunya cara yang diterima secara umum untuk merujuk ke properti objek adalah string dengan namanya. Pustaka yang mendasarinya menggunakan refleksi atau introspeksi untuk mencari metode atau bidang aksesor. Untuk referensi objek bersarang, notasi berikut biasanya digunakan:
person.contact.address.city
Masalah dengan metode ini adalah kurangnya kontrol atas ejaan nama dan jenis properti dengan semua yang tersirat:
- Tidak ada kontrol kesalahan pada tahap kompilasi. Anda dapat membuat kesalahan dalam nama, Anda dapat menerapkannya ke kelas yang salah, jenis properti tidak dikontrol. Kita juga harus menulis tes yang sangat bodoh.
- Tidak ada dukungan dari IDE. Sangat lelah ketika Anda membuat 200+ bidang. Baik jika ada Juni untuk ini, yang bisa dimatikan.
- Refactoring kode canggih. Ubah nama bidang, dan pada saat yang sama banyak hal akan jatuh. IDE yang baik juga akan memunculkan ratusan tempat di mana kata yang sama muncul.
- Dukungan dan analisis kode. Kami ingin melihat di mana properti itu digunakan, tetapi "Temukan Penggunaan" tidak akan menampilkan string.
Akibatnya, kami masih ingin memiliki referensi properti aman tipe statis. Seorang pengambil adalah kandidat terbaik untuk peran ini, karena:
- Terikat ke kelas tertentu
- Berisi nama properti.
- Memiliki tipe
Bagaimana saya bisa merujuk pada seorang pengambil?
Proksi
Salah satu cara yang menarik adalah proksi (atau mengolok-olok) objek untuk mencegat rantai panggilan pengambil, yang digunakan di beberapa perpustakaan: Mockito , QueryDSL , BeanPath . Tentang yang terakhir di Habrรฉ ada artikel dari penulis.
Idenya cukup sederhana, tetapi tidak sepele untuk diimplementasikan (contoh dari artikel yang disebutkan).
Account account = root(Account.class); tableBuilder.addColumn( $( account.getCustomer().getName() ) );
Menggunakan pembuatan kode dinamis, kelas proksi khusus dibuat yang mewarisi dari kelas kacang dan memotong semua panggilan pengambil dalam rantai, membangun jalur di variabel ThreadLocal. Dalam hal ini, panggilan objek getter ini tidak terjadi.
Pada artikel ini kami akan mempertimbangkan metode alternatif.
Tautan Metode
Dengan munculnya Java 8, lambdas dan kemampuan untuk menggunakan referensi metode muncul. Karena itu, wajar jika memiliki sesuatu seperti:
Person person = โฆ assertEquals("name", $(Person::getName).getPath());
Metode $ menerima lambda berikut di mana referensi pengambil dilewatkan:
public interface MethodReferenceLambda<BEAN, TYPE> extends Function<BEAN, TYPE>, Serializable {} ... public static <BEAN, TYPE> BeanProperty<BEAN, TYPE> $(MethodReferenceLambda<BEAN, TYPE> methodReferenceLambda)
Masalahnya adalah bahwa karena penghapusan tipe, tidak ada cara untuk mendapatkan jenis BEAN dan TYPE di runtime, dan juga tidak ada informasi tentang nama pengambil: metode yang disebut "luar" adalah Function.apply ().
Namun demikian, ada trik tertentu - ini adalah penggunaan lambda serial.
MethodReferenceLambda<Person,String> lambda = Person::getName(); Method writeMethod = lambda.getClass().getDeclaredMethod("writeReplace"); writeMethod.setAccessible(true); SerializedLambda serLambda = (SerializedLambda) writeMethod.invoke(lambda); String className = serLambda.getImplClass().replaceAll("/", "."); String methodName = serLambda.getImplMethodName();
Kelas SerializedLambda berisi semua informasi yang diperlukan tentang kelas dan metode yang dipanggil. Berikutnya adalah masalah teknologi.
Karena saya banyak bekerja dengan struktur data, metode ini mendorong saya untuk menulis perpustakaan kecil untuk akses statis ke properti.
Perpustakaan BeanRef
Menggunakan perpustakaan terlihat seperti ini:
Person person = ...
dan tidak memerlukan keajaiban pembuatan kode dan dependensi pihak ketiga. Alih-alih rantai rajin, rantai lambda digunakan dengan referensi untuk rajin. Pada saat yang sama, keamanan jenis dihormati dan penyelesaian otomatis berbasis IDE bekerja dengan baik:

Anda dapat menggunakan nama pengambil dalam notasi standar (getXXX () / isXXX ()) dan non-standar (xxx ()). Perpustakaan akan mencoba untuk menemukan setter yang sesuai, dan jika tidak ada, maka properti dinyatakan read-only.
Untuk mempercepat kinerja, properti yang diselesaikan di-cache, dan ketika Anda menyebutnya lagi dengan lambda yang sama, hasilnya sudah disimpan.
Selain nama properti / jalur, menggunakan objek BeanPath, Anda dapat mengakses nilai properti objek:
Person person = ... final BeanPath<Person, String> personCityProperty = $(Person::getContact).$(Contact::getAddress).$(Address::getCity); String personCity = personCityProperty.get(person);
Selain itu, jika objek perantara dalam rantai adalah nol, maka panggilan yang sesuai juga akan mengembalikan nol, bukan NPE. Ini akan sangat menyederhanakan kode tanpa memerlukan verifikasi.
Melalui BeanPath, Anda juga dapat mengubah nilai properti objek jika tidak hanya baca:
personCityProperty.set(person, โMadridโ);
Mengikuti ide yang sama - sesedikit NPE mungkin - dalam hal ini, jika salah satu objek perantara dalam rantai adalah nol, pustaka akan mencoba secara otomatis membuat dan menyimpannya di bidang. Untuk melakukan ini, properti terkait harus dapat ditulis, dan kelas objek harus memiliki konstruktor publik tanpa parameter.
Sebagai fitur eksperimental, peluang untuk bekerja dengan koleksi ditawarkan. Untuk beberapa kasus khusus, kadang-kadang diperlukan untuk membuat jalur, merujuk ke objek dalam koleksi. Untuk melakukan ini, metode $$ disediakan, yang membangun tautan ke elemen terakhir dari koleksi (menganggapnya sebagai satu-satunya).
final BeanPath<Person, String> personPhonePath = $(Person::getContact).$$(Contact::getPhoneList).$(Phone::getPhone); assertEquals("contact.phoneList.phone", personPhonePath.getPath()); assertEquals(personPhonePath.get(person), person.getContact().getPhoneList() .get(person.getContact().getPhoneList().size()-1).getPhone());
Proyek ini dihosting di sini: https://github.com/throwable/beanref , binari tersedia dari repositori jcenter maven.
Berguna
java.beans.Introspector
Kelas Introspektor dari Java Java standar memungkinkan Anda untuk menyelesaikan properti bin.
Apache Commons BeanUtils
Perpustakaan paling komprehensif untuk bekerja dengan Java Beans.
Beanpath
Pustaka yang disebutkan melakukan hal yang sama melalui proxy.
Objenesis
Kami instantiate objek dari kelas apa pun dengan set konstruktor.
QueryDSL Alias
Menggunakan kelas proksi untuk menetapkan kriteria di QueryDSL
Jinq
Perpustakaan yang menarik yang menggunakan lambdas untuk menetapkan kriteria di JPA. Banyak keajaiban: proksi, serialisasi lambdas, menafsirkan bytecode.