Diposting oleh Clément Béra, Insinyur perangkat lunak senior

Record adalah fitur Java baru untuk class pembawa data yang tidak dapat diubah yang diperkenalkan di Java 16 dan Android 14. Untuk menggunakan record di Android Studio Flamingo, Anda memerlukan SDK Android 14 (API level 34) sehingga class java.lang.Record ada di android. stoples. Ini tersedia dari “Android UpsideDownCake Preview” SDK revisi 4. Record pada dasarnya adalah kelas dengan properti yang tidak dapat diubah dan metode hashCode, equals, dan toString implisit berdasarkan bidang data yang mendasarinya. Dalam hal itu mereka sangat mirip dengan kelas data Kotlin. Untuk mendeklarasikan rekaman Person dengan bidang Nama string dan usia int untuk dikompilasi ke rekaman Java, gunakan kode berikut:

@JvmRecord data class Person(nama val: String, val usia: Int)

File build.gradle juga perlu diperluas untuk menggunakan sumber dan target SDK dan Java yang benar. Saat ini Pratinjau UpsideDownCake Android diperlukan, tetapi ketika SDK final Android 14 dirilis, gunakan “compileSdk 34” dan “targetSdk 34” sebagai pengganti versi pratinjau.

android { compileSdkPreview “UpsideDownCake” defaultConfig { targetSdkPreview “UpsideDownCake” } compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { jvmTarget = ’17’ } }

Record tidak selalu memberikan nilai dibandingkan dengan class data dalam program Kotlin murni, tetapi record memungkinkan program Kotlin berinteraksi dengan library Java yang API-nya menyertakan record. Untuk pemrogram Java, ini memungkinkan kode Java untuk menggunakan catatan. Gunakan kode berikut untuk mendeklarasikan record yang sama di Java:

catatan publik Orang (Nama string, usia int) {}

Selain flag dan atribut record, Person record kira-kira setara dengan class berikut yang dijelaskan menggunakan sumber Kotlin:

class PersonEquivalent(val name: String, val age: Int) { override fun hashCode() : Int { return 31 * (31 * PersonEquivalent::class.hashCode() + name.hashCode()) + Integer.hashCode(age) } timpa fun equals(other: Any?) : Boolean { if (other == null || other ! is PersonEquivalent) { return false } return name == other.name && age == other.age } override fun toString() : String { return String.format( PersonEquivalent::class.java.simpleName + “[name=%s, age=%s]”, nama, umur.toString() ) } } println(Person(“John”, 42).toString()) >>> Orang[name=John, age=42]

Dimungkinkan dalam kelas rekaman untuk mengganti metode hashCode, equals, dan toString, yang secara efektif menggantikan metode yang dihasilkan runtime JVM. Dalam hal ini, perilaku ditentukan oleh pengguna untuk metode ini.

Rekam desugaring

Karena catatan tidak didukung pada perangkat Android mana pun saat ini, mesin desugaring D8/R8 perlu menghapus catatan: ini mengubah kode catatan menjadi kode yang kompatibel dengan VM Android. Desugaring rekaman melibatkan pengubahan rekaman menjadi kelas yang kira-kira setara, tanpa membuat atau menyusun sumber. Sumber Kotlin berikut menunjukkan perkiraan kode yang dihasilkan. Agar ukuran kode aplikasi tetap kecil, rekaman didesugasikan sehingga metode pembantu dibagikan di antara rekaman.

class PersonDesugared(val name: String, val age: Int) { fun getFieldsAsObjects(): Array { return arrayOf(name, age) } timpa fun hashCode(): Int { return SharedRecordHelper.hash( PersonDesugared::class. java, getFieldsAsObjects()) } timpa fun equals(other: Any?): Boolean { if (other == null || other ! is PersonDesugared) { return false } return getFieldsAsObjects().contentEquals(other.getFieldsAsObjects()) } timpa fun toString(): String { return SharedRecordHelper.toString( getFieldsAsObjects(), PersonDesugared::class.java, “name;age”) } class SharedRecordHelper { objek pendamping { fun hash(recordClass: Class<*>, fieldValues: Array ): Int { return 31 * recordClass.hashCode() + fieldValues.contentHashCode() } fun toString( fieldValues: Array, recordClass: Class<*>, fieldNames: String ): String { val fieldNamesSplit: Daftar = if (fieldNames.isEmpty()) emptyList() else fieldNames.split(“;”) val builder: StringBuilder = StringBuilder() builder.append(recordClass.simpleName).append(“[“)
for (i in fieldNamesSplit.indices) {
builder
.append(fieldNamesSplit[i]) .append(“=”) .append(fieldValues[i]) if (i != fieldNamesSplit.size – 1) { builder.append(“, “) } } builder.append(“]”) return builder.toString() } } } }

Rekam penyusutan

R8 mengasumsikan bahwa metode hashCode, equals, dan toString default yang dihasilkan oleh javac secara efektif mewakili keadaan internal rekaman. Oleh karena itu, jika suatu bidang diperkecil, metode harus mencerminkan hal itu; toString harus mencetak nama yang diperkecil. Jika suatu bidang dihapus, misalnya karena memiliki nilai konstan di semua instance, maka metode harus mencerminkan hal itu; bidang diabaikan oleh metode hashCode, equals, dan toString. Saat R8 menggunakan struktur rekaman dalam metode yang dihasilkan oleh javac, misalnya saat mencari bidang dalam rekaman atau memeriksa struktur rekaman yang dicetak, R8 menggunakan refleksi. Seperti halnya penggunaan pantulan apa pun, Anda harus menulis aturan simpan untuk memberi tahu penyusut tentang penggunaan pantulan sehingga dapat mempertahankan struktur.

Dalam contoh kita, asumsikan bahwa usia adalah konstanta 42 di seluruh aplikasi sementara name tidak konstan di seluruh aplikasi. Kemudian toString mengembalikan hasil yang berbeda tergantung pada aturan yang Anda tetapkan:

Orang (“John”, 42).toString(); >>> Orang[name=John, age=42]

>>> a[a=John]

>>> Orang[b=John]

>>> a[name=John]

>>> a[a=John, b=42]

>>> Orang[name=John, age=42]

Kasus penggunaan reflektif

Pertahankan ke perilaku String

Katakanlah Anda memiliki kode yang menggunakan pencetakan catatan yang tepat dan mengharapkannya tidak berubah. Untuk itu Anda harus menyimpan seluruh isi field record dengan aturan seperti:

-menjaga,mengizinkan menyusut Orang kelas -menjaga anggota kelas,mengizinkan pengoptimalan kelas Orang { ; }

Ini memastikan bahwa jika rekaman Person dipertahankan dalam output, panggilan toString apa pun akan menghasilkan string yang sama persis seperti di program aslinya. Misalnya:

Person(“John”, 42).toString(); >>> Orang[name=John, age=42]

Namun, jika Anda hanya ingin mempertahankan pencetakan untuk bidang yang benar-benar digunakan, Anda dapat membiarkan bidang yang tidak digunakan dihapus atau diciutkan dengan memungkinkan menyusut:

-menjaga,mengizinkan menyusut Orang kelas -menjaga anggota kelas,memungkinkan menyusut,mengizinkan pengoptimalan kelas Orang { ; }

Dengan aturan ini, kompiler menghapus kolom usia:

Person(“John”, 42).toString(); >>> Orang[name=John]

Pertahankan anggota rekaman untuk pencarian reflektif

Jika Anda perlu mengakses anggota rekaman secara reflektif, biasanya Anda perlu mengakses metode pengaksesnya. Untuk itu Anda harus menyimpan metode pengakses:

-menjaga,mengizinkan menyusut Orang kelas -menjaga anggota kelas,mengizinkan pengoptimalan kelas Orang { nama java.lang.String(); }

Sekarang jika instance Person berada dalam program sisa, Anda dapat dengan aman mencari keberadaan pengakses secara reflektif:

Person(“John”, 42)::class.java.getDeclaredMethod(“nama”).invoke(obj); >>> Yohanes

Perhatikan bahwa kode sebelumnya mengakses field record menggunakan accessor. Untuk akses bidang langsung, Anda perlu menyimpan bidang itu sendiri:

-keep,izinkan penyusutan kelas Person -keepclassmembers,allowoptimization class Person { nama java.lang.String; }

Bangun sistem dan kelas Rekam

Jika Anda menggunakan sistem build selain AGP, penggunaan record mungkin mengharuskan Anda mengadaptasi sistem build. Kelas java.lang.Record tidak ada hingga Android 14, diperkenalkan di SDK dari revisi “Android UpsideDownCake Preview” 4. D8/R8 memperkenalkan com.android.tools.r8.RecordTag, kelas kosong, untuk menunjukkan bahwa record subclass adalah record. RecordTag digunakan agar instruksi yang mereferensikan java.lang.Record dapat langsung ditulis ulang dengan desugaring ke referensi RecordTag dan tetap berfungsi (instanceof, metode, dan tanda tangan bidang, dll.).

Ini berarti bahwa setiap bangunan yang berisi referensi ke java.lang.Record menghasilkan kelas RecordTag sintetik. Dalam situasi di mana aplikasi dipecah menjadi pecahan, setiap pecahan dikompilasi menjadi file dex, dan file dex disatukan tanpa digabungkan dalam aplikasi Android, ini dapat menyebabkan duplikat kelas RecordTag.

Untuk menghindari masalah ini, build perantara D8 apa pun menghasilkan kelas RecordTag sebagai sintetik global, dalam output yang berbeda dari file dex. Langkah penggabungan dex kemudian dapat menggabungkan sintetik global dengan benar untuk menghindari perilaku runtime yang tidak terduga. Setiap sistem build yang menggunakan beberapa kompilasi seperti output sharding atau perantara diperlukan untuk mendukung sintetik global agar berfungsi dengan benar. AGP sepenuhnya mendukung catatan dari versi 8.1.