Refleksi dengan Kotlin

1. Pengenalan

Refleksi adalah nama untuk kemampuan memeriksa, memuat dan berinteraksi dengan kelas, medan, dan kaedah pada waktu runtime. Kita dapat melakukan ini walaupun kita tidak tahu apa yang ada pada masa penyusunan.

Ini mempunyai sejumlah besar penggunaan, bergantung pada apa yang sedang kita kembangkan. Sebagai contoh, kerangka kerja seperti Spring banyak menggunakannya.

Sokongan untuk ini dimasukkan ke dalam JVM dan dengan itu tersedia secara tersirat untuk semua bahasa berdasarkan JVM. Walau bagaimanapun, beberapa bahasa JVM mempunyai sokongan tambahan daripada apa yang sudah tersedia.

2. Refleksi Java

Semua konstruksi Java Reflection standard tersedia dan berfungsi dengan baik dengan kod Kotlin kami . Ini termasuk kelas java.lang.Class serta semua yang ada dalam pakej java.lang.reflect .

Jika kita ingin menggunakan API Refleksi Java standar untuk alasan apa pun, kita dapat melakukannya dengan cara yang sama seperti yang kita lakukan di Java. Sebagai contoh, untuk mendapatkan senarai semua kaedah awam dalam kelas Kotlin yang akan kami lakukan:

MyClass::class.java.methods

Ini terbahagi kepada konstruk berikut:

  • Kelas MyClass :: memberi kita perwakilan Kelas Kotlin untuk kelas MyClass
  • .java memberi kita setara dengan java.lang.Class
  • .methods adalah panggilan ke kaedah aksesor java.lang.Class.getMethods ()

Ini akan berfungsi sama seperti dipanggil dari Java atau Kotlin, dan sama ada dipanggil di kelas Java atau Kotlin . Ini termasuk konstruksi khusus Kotlin, seperti Kelas Data.

data class ExampleDataClass( val name: String, var enabled: Boolean) ExampleDataClass::class.java.methods.forEach(::println)

Kotlin menukar jenis yang dikembalikan ke perwakilan Kotlin juga.

Di atas, kita mendapat kotlin. Array yang boleh kita panggil untuk Setiap ().

3. Peningkatan Refleksi Kotlin

Walaupun kami dapat menggunakan API Java Reflection standard, ia tidak mengetahui semua sambungan yang dibawa oleh Kotlin ke platform .

Selain itu, kadang-kadang agak janggal untuk digunakan dalam beberapa keadaan. Kotlin membawa API refleksi sendiri yang dapat kita gunakan untuk menyelesaikan masalah ini.

Semua titik masuk ke Kotlin Reflection API menggunakan Rujukan. Sebelumnya, kami melihat penggunaan :: class untuk memberi rujukan kepada definisi Kelas. Kami juga dapat menggunakannya untuk mendapatkan rujukan kaedah dan sifat.

3.1. Rujukan Kelas Kotlin

API Refleksi Kotlin membenarkan akses ke rujukan Kelas. Ini kemudian boleh digunakan untuk memeriksa maklumat lengkap kelas Kotlin . Ini memberikan akses ke rujukan Kelas Java - objek java.lang.Class - tetapi juga ke semua butiran khusus Kotlin.

API Kotlin untuk perincian kelas berpusat di sekitar kelas kotlin.reflect.KClass . Ini dapat diakses dengan menggunakan :: operator dari mana-mana nama kelas atau contoh - contohnya String :: class.

Sebagai alternatif, ia dapat diakses dengan menggunakan kaedah peluasan java.lang.Class.kotlin jika contoh Kelas Java tersedia untuk kami:

val listClass: KClass = List::class val name = "Baeldung" val stringClass: KClass = name::class val someClass: Class val kotlinClass: KClass = someClass.kotlin

Setelah kami memperoleh objek KClass , ada beberapa perkara mudah yang dapat memberitahu kami mengenai kelas yang dimaksudkan . Sebahagian daripadanya adalah konsep Java standard, dan yang lain adalah konsep khusus Kotlin.

Sebagai contoh, kita dapat mengetahui dengan mudah sama ada Kelas itu Abstrak atau Akhir, tetapi kita juga dapat mengetahui sama ada Kelas itu Kelas Data atau Kelas Pendamping:

val stringClass = String::class assertEquals("kotlin.String", stringClass.qualifiedName) assertFalse(stringClass.isData) assertFalse(stringClass.isCompanion) assertFalse(stringClass.isAbstract) assertTrue(stringClass.isFinal) assertFalse(stringClass.isSealed)

Kami juga mempunyai cara untuk bergerak di sekitar hierarki kelas. Di Jawa, kita sudah dapat berpindah dari Kelas ke kelas super, antarmuka dan kelas luar yang tertutup - jika sesuai.

Kotlin menambahkan kemampuan ini untuk mendapatkan Objek Pengiring untuk kelas sewenang-wenangnya, dan contoh Objek untuk kelas Objek:

println(TestWithCompanion::class.companionObject) println(TestWithCompanion::class.companionObjectInstance) println(TestObject::class.objectInstance)

Kita boleh membuat contoh kelas baru dari Rujukan Kelas juga , dengan cara yang sama seperti di Java:

val listClass = ArrayList::class val list = listClass.createInstance() assertTrue(list is ArrayList)

Sebagai alternatif, kita boleh mengakses konstruktor dan menggunakan yang eksplisit jika perlu. Ini semua rujukan Kaedah seperti yang dibincangkan di bahagian seterusnya.

Dengan cara yang sangat serupa, kita boleh mendapatkan akses ke semua kaedah, sifat, sambungan dan ahli kelas yang lain:

val bigDecimalClass = BigDecimal::class println(bigDecimalClass.constructors) println(bigDecimalClass.functions) println(bigDecimalClass.memberProperties) println(bigDecimalClass.memberExtensionFunctions)

3.2. Rujukan Kaedah Kotlin

Selain dapat berinteraksi dengan Kelas, kita juga dapat berinteraksi dengan Kaedah dan Sifat .

Ini merangkumi sifat kelas - ditentukan dengan val atau var , kaedah kelas standard, dan fungsi tingkat atas. Seperti sebelumnya, ini berfungsi dengan baik pada kod yang ditulis di Java standard seperti pada kod yang ditulis di Kotlin.

Dengan cara yang sama persis dengan kelas, kita dapat memperoleh rujukan kepada Kaedah atau Harta menggunakan :: operator .

Ini sama persis seperti di Java 8 untuk mendapatkan rujukan metode, dan kita dapat menggunakannya dengan cara yang sama. Namun, di Kotlin kaedah rujukan ini juga dapat digunakan untuk mendapatkan maklumat refleksi mengenai sasaran.

Setelah memperoleh rujukan kaedah, kita dapat menyebutnya seolah-olah benar-benar metode yang dimaksudkan . Ini dikenali sebagai Rujukan yang Boleh Dipanggil:

val str = "Hello" val lengthMethod = str::length assertEquals(5, lengthMethod())

We can also get more details about the method itself, in the same way, that we can for classes. This includes both standard Java details as well as Kotlin specific details such as if the method is an operator or if it's inline:

val byteInputStream = String::byteInputStream assertEquals("byteInputStream", byteInputStream.name) assertFalse(byteInputStream.isSuspend) assertFalse(byteInputStream.isExternal) assertTrue(byteInputStream.isInline) assertFalse(byteInputStream.isOperator)

In addition to this, we can get more information about the inputs and outputs of the method through this reference.

This includes details about the return type and the parameters, including Kotlin specific details – such as nullability and optionality.

val str = "Hello" val method = str::byteInputStream assertEquals( ByteArrayInputStream::class.starProjectedType, method.returnType) assertFalse(method.returnType.isMarkedNullable) assertEquals(1, method.parameters.size) assertTrue(method.parameters[0].isOptional) assertFalse(method.parameters[0].isVararg) assertEquals( Charset::class.starProjectedType, method.parameters[0].type)

3.3. Kotlin Property References

This works exactly the same for Properties as well, though obviously, the details that can be obtained are different. Properties instead can inform us if they are constants, late initialized or mutable:

lateinit var mutableProperty: String val mProperty = this::mutableProperty assertEquals("mutableProperty", mProperty.name) assertTrue(mProperty.isLateinit) assertFalse(mProperty.isConst) assertTrue(mProperty is KMutableProperty)

Note that the concept of Properties also works in any non-Kotlin code. These are identified by fields that follow the JavaBeans conventions regarding getter and setter methods.

This includes classes in the Java standard library. For example, the Throwable class has a Property Throwable.message by virtue of the fact that there is a method getMessage() defined in it.

We can access the actual Property through Method references that are exposed – the getter and setter methods. The setter is only available if we are working with a KMutableProperty – i.e. the property was declared as var, whereas the getter is always available.

These are exposed in an easier to use way via the get() and set() methods. The getter and setter values are actual method references, allowing us to work with them exactly the same as any other method reference:

val prop = this::mutableProperty assertEquals( String::class.starProjectedType, prop.getter.returnType) prop.set("Hello") assertEquals("Hello", prop.get()) prop.setter("World") assertEquals("World", prop.getter())

4. Summary

Artikel ini memberikan gambaran umum mengenai beberapa perkara yang dapat dicapai dengan refleksi di Kotlin, termasuk bagaimana ia berinteraksi dan berbeza dengan kemampuan refleksi yang dibina dalam bahasa Java standard.

Semua contoh boleh didapati di GitHub.