Bagaimana cara kerja kata kunci yang dibenarkan di Kotlin?

144

Saya mencoba memahami tujuan reifiedkata kunci, tampaknya itu memungkinkan kami untuk melakukan refleksi pada obat generik .

Namun, ketika saya tinggalkan itu berfungsi dengan baik. Adakah yang mau menjelaskan kapan ini membuat perbedaan nyata ?

hl3mukkel
sumber
Anda yakin tahu apa itu refleksi? Juga, apakah Anda mendengar tentang penghapusan tipe?
Mibac
3
Parameter tipe generik dihapus saat runtime, baca tentang penghapusan tipe jika Anda belum melakukannya. Parameter tipe reified pada fungsi inline tidak hanya inline body metode, tetapi juga parameter tipe generik yang memungkinkan Anda melakukan hal-hal seperti T :: class.java (yang tidak dapat Anda lakukan dengan tipe generik normal). Dimasukkan sebagai komentar karena saya tidak punya waktu untuk menyempurnakan jawaban penuh sekarang ..
F. George
Hal ini memungkinkan untuk mendapatkan akses ke tipe generik konkret dari suatu fungsi tanpa mengandalkan refleksi dan tanpa harus melewati tipe tersebut sebagai argumen.
BladeCoder

Jawaban:

368

TL; DR: Apa yang reifiedbaik untuk

fun <T> myGenericFun(c: Class<T>) 

Dalam tubuh fungsi generik seperti myGenericFun, Anda tidak dapat mengakses tipe Tkarena hanya tersedia pada waktu kompilasi tetapi terhapus saat runtime. Oleh karena itu, jika Anda ingin menggunakan tipe generik sebagai kelas normal di fungsi tubuh Anda harus secara eksplisit lulus kelas sebagai parameter seperti yang ditunjukkan pada myGenericFun.

Jika Anda membuat inlinefungsi dengan reified T , tipe Tdapat diakses bahkan saat runtime dan dengan demikian Anda tidak perlu meneruskannya Class<T>. Anda dapat bekerja dengan Tseolah-olah itu adalah kelas normal, misalnya Anda mungkin ingin memeriksa apakah suatu variabel adalah contoh dari T yang Anda dapat dengan mudah melakukan itu: myVar is T.

Seperti inlinefungsi dengan reifiedtipe Tpenampilan sebagai berikut:

inline fun <reified T> myGenericFun()

Bagaimana cara reifiedkerjanya

Anda hanya dapat menggunakan reifieddalam kombinasi dengan suatu inlinefungsi . Fungsi seperti itu membuat compiler menyalin bytecode fungsi ke setiap tempat di mana fungsi tersebut digunakan (fungsi tersebut sedang "digarisbawahi"). Ketika Anda memanggil fungsi inline dengan tipe reified, kompiler mengetahui tipe aktual yang digunakan sebagai argumen tipe dan memodifikasi bytecode yang dihasilkan untuk menggunakan kelas terkait secara langsung. Oleh karena itu panggilan seperti myVar is Tmenjadi myVar is String(jika argumen tipe String) di bytecode dan saat runtime.


Contoh

Mari kita lihat contoh yang menunjukkan betapa bermanfaatnya reifiedhal itu. Kami ingin membuat fungsi ekstensi untuk Stringdipanggil toKotlinObjectyang mencoba untuk mengkonversi string JSON ke objek Kotlin biasa dengan tipe yang ditentukan oleh tipe generik fungsi T. Kita dapat menggunakan com.fasterxml.jackson.module.kotlinini dan pendekatan pertama adalah sebagai berikut:

a) Pendekatan pertama tanpa tipe reified

fun <T> String.toKotlinObject(): T {
      val mapper = jacksonObjectMapper()
                                                        //does not compile!
      return mapper.readValue(this, T::class.java)
}

The readValueMetode mengambil jenis yang seharusnya untuk mengurai JsonObjectke. Jika kita mencoba untuk mendapatkan Classparameter tipe T, kompiler mengeluh: "Tidak dapat menggunakan 'T' sebagai parameter tipe reified. Gunakan kelas sebagai gantinya."

b) Penanganan masalah dengan Classparameter eksplisit

fun <T: Any> String.toKotlinObject(c: KClass<T>): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(this, c.java)
}

Sebagai solusinya, Classdari Tdapat dibuat parameter metode, yang kemudian digunakan sebagai argumen untuk readValue. Ini berfungsi dan merupakan pola umum dalam kode Java generik. Itu bisa disebut sebagai berikut:

data class MyJsonType(val name: String)

val json = """{"name":"example"}"""
json.toKotlinObject(MyJsonType::class)

c) Cara Kotlin: reified

Menggunakan inlinefungsi dengan reifiedparameter tipe Tmemungkinkan untuk mengimplementasikan fungsi secara berbeda:

inline fun <reified T: Any> String.toKotlinObject(): T {
    val mapper = jacksonObjectMapper()
    return mapper.readValue(this, T::class.java)
}

Tidak perlu untuk mengambil Classdari Ttambahan, Tdapat digunakan sebagai olah itu adalah kelas biasa. Untuk klien, kode ini terlihat seperti ini:

json.toKotlinObject<MyJsonType>()

Catatan Penting: Bekerja dengan Java

Fungsi sebaris dengan reifiedtipe tidak dapat dipanggil dari kode Java .

s1m0nw1
sumber
6
Terima kasih atas tanggapan komprehensif Anda! Itu sebenarnya masuk akal. Hanya satu hal yang saya bertanya-tanya, mengapa diperlukan reified jika fungsi tersebut sedang inline? Itu akan meninggalkan tipe penghapusan dan fungsi sebaris pula? Ini agak menyia-nyiakan bagi saya, jika Anda inline fungsi Anda mungkin juga inline jenis yang digunakan atau saya melihat sesuatu yang salah di sini?
hl3mukkel
6
Terima kasih atas umpan balik Anda, sebenarnya saya lupa menyebutkan sesuatu yang mungkin memberi Anda jawabannya: fungsi inline normal dapat dipanggil dari Jawa tetapi yang dengan parameter tipe reified tidak bisa! Saya pikir ini adalah alasan mengapa tidak setiap parameter tipe fungsi inline secara otomatis dibuat terverifikasi.
s1m0nw1
Bagaimana jika fungsinya merupakan campuran dari parameter reified dan non-reified? Itu membuatnya tidak memenuhi syarat untuk dipanggil dari Jawa, mengapa tidak memverifikasi semua jenis parameter secara otomatis? Mengapa kotlin perlu ditentukan ulang untuk semua parameter tipe secara eksplisit?
Vairavan
1
bagaimana jika penelepon atas dalam tumpukan tidak perlu json.toKotlinObject <MyJsonType> (), tetapi json.toKotlinObject <T> () untuk objek yang berbeda?
tujuh
1

SEDERHANA

* Reified adalah untuk memberikan izin untuk digunakan pada waktu kompilasi (untuk mengakses fungsi T inside de)

misalnya:

 inline fun <reified T:Any>  String.convertToObject(): T{

    val gson = Gson()

    return gson.fromJson(this,T::class.java)

}

menggunakan like:

val jsonStringResponse = "{"name":"bruno" , "age":"14" , "world":"mars"}"
val userObject = jsonStringResponse.convertToObject<User>()
  println(user.name)
poiu
sumber