Як працює ключове слово в Котліні?


144

Я намагаюся зрозуміти призначення reifiedключового слова, мабуть, це дозволяє нам задуматися про генеричні дані .

Однак, коли я залишаю його, це працює так само добре. Будь-догляд пояснити , коли це робить фактичну різницю ?


Ви впевнені, що знаєте, що таке відображення? Також ви чули про стирання типу?
Мібак

3
Параметри загального типу стираються під час виконання, читайте про стирання типу, якщо ви ще цього не зробили. Параметри типового типу для вбудованих функцій не тільки вбудовують тіло методу, але й параметр загального типу, що дозволяє робити такі речі, як T :: class.java (що не можна робити із звичайними загальними типами). Зауважуючи як коментар, тому що я не встигаю викласти повну відповідь зараз
Ф. Джордж

Це дозволяє отримати доступ до конкретного загального типу функції, не покладаючись на рефлексію та без необхідності передавати тип як аргумент.
BladeCoder

Відповіді:


368

TL; DR: Для чого reifiedдобре

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

У тілі такої загальної функції, як myGenericFunви, ви не можете отримати доступ до типу, Tоскільки він доступний лише під час компіляції, але стирається під час виконання. Тому, якщо ви хочете використовувати загальний тип як нормальний клас у тілі функції, вам потрібно явно передати клас як параметр, як показано на рисунку myGenericFun.

Якщо ви створюєте inlineфункцію із вдосконаленою T формою, до цього типу Tможна отримати доступ навіть під час виконання, і тому вам не потрібно додатково передавати Class<T>. Ви можете працювати з Tяк якби це був звичайний клас, наприклад , ви можете захотіти , щоб перевірити , є чи змінна є екземпляром T , який ви можете легко зробити , то: myVar is T.

Така inlineфункція з reifiedтипом Tвиглядає так:

inline fun <reified T> myGenericFun()

Як reifiedпрацює

Можна використовувати лише reifiedв поєднанні з inlineфункцією . Така функція змушує компілятор скопіювати байт-код функції в будь-яке місце, де функція використовується (функція "вбудована"). Коли ви викликаєте вбудовану функцію з рефікованим типом, компілятор знає фактичний тип, що використовується як аргумент типу, і змінює створений байтовий код, щоб безпосередньо використовувати відповідний клас. Тому виклики на зразок myVar is Tстають myVar is String(якщо аргумент типу String) були в байт-коді та під час виконання.


Приклад

Давайте подивимось на приклад, який показує, наскільки корисними reifiedможуть бути. Ми хочемо створити функцію розширення для Stringвиклику, toKotlinObjectякий намагається перетворити рядок JSON в звичайний об'єкт Kotlin з типом, визначеним загальним типом функції T. Ми можемо використовувати com.fasterxml.jackson.module.kotlinдля цього, і перший підхід полягає в наступному:

а) Перший підхід без рефікованого типу

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

readValueМетод приймає тип , який він , як передбачається розібрати JsonObjectк. Якщо ми спробуємо отримати Classпараметр типу T, компілятор скаржиться: "Неможливо використовувати" T "як параметр рефікованого типу. Замість цього використовуйте клас."

б) Обхід із явним Classпараметром

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

Щоб обійти цю проблему, то Classз Tможе бути зроблений параметр методу, який потім використовується в якості аргументу readValue. Це працює і є загальною схемою в загальному Java-коді. Його можна назвати так:

data class MyJsonType(val name: String)

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

c) Котлінський шлях: reified

Використання inlineфункції з reifiedпараметром типу Tдозволяє реалізувати функцію по-різному:

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

Там немає необхідності брати Classз Tдодатково, Tможе бути використаний , як якщо б це був звичайний клас. Для клієнта код виглядає так:

json.toKotlinObject<MyJsonType>()

Важлива примітка: Робота з Java

Вбудована функція з reifiedтипом не можна викликати з коду Java .


6
Дякуємо за всебічну відповідь! Це насправді має сенс. Мене цікавить лише одне, чому потрібна повторна редакція, якщо функція все-таки вбудована? Це дозволить залишити стирання типу і все одно вбудувати функцію? Мені це здається марним, якщо ви вбудуєте функцію, яку ви могли б також встановити тип, що використовується, або я бачу щось не так?
hl3mukkel

6
Дякую за Ваш відгук, я фактично забуваю згадати щось, що могло б дати вам відповідь: нормальна вбудована функція може бути викликана з Java, але функція із зміненим параметром типу не може! Я думаю, це причина, чому не кожен тип параметра вбудованої функції автоматично переробляється наново.
s1m0nw1

Що робити, якщо функція - це поєднання змінених та нереалізованих параметрів? Це робить його непридатним викликати з Java все-таки, чому б не змінити всі параметри типу автоматично? Чому kotlin потрібно явно переймати вказані для всіх параметрів типу?
Вайраван

1
що робити, якщо верхнім абонентам у стеку потрібні не json.toKotlinObject <MyJsonType> (), а json.toKotlinObject <T> () для різних об'єктів?
сім

1

ПРОСТО

* reified - це надання дозволу на використання під час компіляції (для доступу до T всередині функції)

наприклад:

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

    val gson = Gson()

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

}

використовуючи:

val jsonStringResponse = "{"name":"bruno" , "age":"14" , "world":"mars"}"
val userObject = jsonStringResponse.convertToObject<User>()
  println(user.name)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.