Refleksi dengan Kotlin

1. Perkenalan

Refleksi adalah nama untuk kemampuan memeriksa, memuat, dan berinteraksi dengan kelas, bidang, dan metode pada waktu proses. Kami dapat melakukan ini bahkan ketika kami tidak tahu apa itu pada waktu kompilasi.

Ini memiliki banyak kegunaan, tergantung pada apa yang kita kembangkan. Misalnya, kerangka kerja seperti Spring memanfaatkannya dengan berat.

Dukungan untuk ini dibangun ke dalam JVM dan dengan demikian tersedia secara implisit untuk semua bahasa berbasis JVM. Namun, beberapa bahasa JVM memiliki dukungan tambahan selain yang sudah tersedia.

2. Refleksi Jawa

Semua konstruksi Java Reflection standar tersedia dan bekerja dengan baik dengan kode Kotlin kami . Ini termasuk kelas java.lang.Class serta semua yang ada di paket java.lang.reflect .

Jika kita ingin menggunakan Java Reflection API standar untuk alasan apa pun, kita dapat melakukannya dengan cara yang persis sama seperti yang kita lakukan di Java. Misalnya, untuk mendapatkan daftar semua metode publik di kelas Kotlin, kami akan melakukan:

MyClass::class.java.methods

Ini dipecah menjadi konstruksi berikut:

  • MyClass :: class memberi kita representasi Kotlin Class untuk kelas MyClass
  • .java memberi kita persamaan java.lang.Class
  • .methods adalah panggilan ke metode aksesor java.lang.Class.getMethods ()

Ini akan bekerja persis sama baik dipanggil dari Java atau Kotlin, dan apakah 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 mengonversi jenis yang dikembalikan ke representasi Kotlin juga.

Di atas, kita mendapatkan kotlin.Array yang bisa kita panggil forEach ().

3. Peningkatan Refleksi Kotlin

Meskipun kami dapat menggunakan Java Reflection API standar, ia tidak mengetahui semua ekstensi yang dibawa Kotlin ke platform .

Selain itu, terkadang agak canggung untuk digunakan dalam beberapa situasi. Kotlin menghadirkan API refleksi sendiri yang dapat kita gunakan untuk memecahkan masalah ini.

Semua titik masuk ke dalam API Refleksi Kotlin menggunakan Referensi. Sebelumnya, kita melihat penggunaan :: class untuk memberikan referensi ke definisi Kelas. Kami juga dapat menggunakan ini untuk mendapatkan referensi ke metode dan properti.

3.1. Referensi Kelas Kotlin

Kotlin Reflection API memungkinkan akses ke referensi Kelas. Ini kemudian dapat digunakan untuk introspeksi detail lengkap kelas Kotlin . Ini memberikan akses ke referensi Kelas Java - objek java.lang.Class - tetapi juga ke semua detail spesifik Kotlin.

API Kotlin untuk detail kelas berpusat di sekitar kelas kotlin.reflect.KClass . Ini bisa diakses dengan menggunakan :: operator dari sembarang nama kelas atau turunan - misalnya String :: class.

Alternatifnya, ini dapat diakses dengan menggunakan metode ekstensi java.lang.Class.kotlin jika instance Java Class tersedia untuk kita:

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

Setelah kita mendapatkan objek KClass , ada beberapa hal sederhana yang dapat diberitahukan kepada kita tentang kelas yang dimaksud . Beberapa di antaranya adalah konsep Java standar, dan lainnya adalah konsep khusus Kotlin.

Misalnya, kita dapat dengan mudah mengetahui apakah suatu Kelas Abstrak atau Final, tetapi kita juga dapat mengetahui apakah Kelas tersebut adalah 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 memiliki cara untuk berpindah di sekitar hierarki kelas. Di Java, kita sudah bisa berpindah dari Kelas ke superclass-nya, antarmuka dan kelas luarnya - jika sesuai.

Kotlin menambahkan kemampuan untuk mendapatkan Companion Object untuk kelas arbitrer, dan instance Object untuk kelas Object:

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

Kita juga dapat membuat instance baru kelas dari Referensi Kelas , dengan cara yang sama seperti di Java:

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

Alternatifnya, kita dapat mengakses konstruktor dan menggunakan yang eksplisit jika perlu. Ini semua adalah referensi Metode seperti yang dibahas di bagian selanjutnya.

Dengan cara yang sangat mirip, kita bisa mendapatkan akses ke semua metode, properti, ekstensi, dan anggota kelas lainnya:

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

3.2. Referensi Metode Kotlin

Selain bisa berinteraksi dengan Classes, kita juga bisa berinteraksi dengan Methods dan Properties .

Ini termasuk properti kelas - didefinisikan dengan val atau var , metode kelas standar, dan fungsi tingkat atas. Seperti sebelumnya, ini bekerja dengan baik pada kode yang ditulis dalam Java standar seperti pada kode yang ditulis di Kotlin.

Dengan cara yang persis sama dengan kelas, kita bisa mendapatkan referensi ke Metode atau Properti menggunakan :: operator .

Ini terlihat persis sama seperti di Java 8 untuk mendapatkan referensi metode, dan kita dapat menggunakannya dengan cara yang persis sama. Namun, di Kotlin, referensi metode ini juga dapat digunakan untuk mendapatkan informasi refleksi tentang target.

Setelah kita memperoleh referensi metode, kita dapat menyebutnya seolah-olah benar-benar metode yang dimaksud . Ini dikenal sebagai Referensi yang Dapat 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 tentang beberapa hal yang dapat dicapai dengan refleksi di Kotlin, termasuk cara interaksi dan perbedaannya dari kapabilitas refleksi yang dibangun ke dalam bahasa Java standar.

Semua contoh tersedia di GitHub.