Как работает ключевое слово reified в Kotlin?

144

Я пытаюсь понять цель reifiedключевого слова, по-видимому, оно позволяет нам размышлять о дженериках .

Однако, когда я оставляю это, это работает так же хорошо. Кто-нибудь хочет объяснить, когда это имеет значение ?

hl3mukkel
источник
Вы уверены, что знаете, что такое отражение? Кроме того, вы слышали о стирании типа?
Мибак
3
Параметры общего типа стираются во время выполнения, читайте об удалении типа, если вы этого еще не сделали. Параметры типа Reified для встроенных функций не только встроены в тело метода, но также и параметр универсального типа, позволяющий вам делать такие вещи, как T :: class.java (чего нельзя делать с обычными универсальными типами). Выкладываю в качестве комментария, потому что у меня нет времени, чтобы прямо сейчас дать полный ответ ..
Ф. Джордж
Это позволяет получить доступ к конкретному универсальному типу функции, не полагаясь на отражение и не передавая тип в качестве аргумента.
BladeCoder

Ответы:

368

TL; DR: что reifiedхорошо для

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

В теле общей функции, такой как myGenericFun, вы не можете получить доступ к типу, Tпотому что он доступен только во время компиляции, но стирается во время выполнения. Поэтому, если вы хотите использовать универсальный тип в качестве обычного класса в теле функции, вам нужно явно передать класс в качестве параметра, как показано в myGenericFun.

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

Такая inlineфункция с reifiedтипом Tвыглядит следующим образом:

inline fun <reified T> myGenericFun()

Как reifiedработает

Вы можете использовать только reifiedв сочетании с inlineфункцией . Такая функция заставляет компилятор копировать байт-код функции в каждое место, где используется функция (функция «встроена»). Когда вы вызываете встроенную функцию с типом reified, компилятор знает фактический тип, используемый в качестве аргумента типа, и модифицирует сгенерированный байт-код для непосредственного использования соответствующего класса. Поэтому вызовы like 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 »в качестве параметра типа reified. Вместо этого используйте класс».

б) Обходной путь с явным 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)

в) путь Котлина: 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 .

s1m0nw1
источник
6
Спасибо за ваш исчерпывающий ответ! Это на самом деле имеет смысл. Только вот что мне интересно: зачем использовать reified, если функция все равно встраивается? Это все равно оставит стирание типа и встроит функцию? Мне это кажется пустой тратой, если вы встроите функцию, вы можете встроить и используемый тип, или я вижу здесь что-то не так?
hl3mukkel
6
Спасибо за ваш отзыв, на самом деле я забыл упомянуть кое-что, что могло бы дать вам ответ: обычная встроенная функция может быть вызвана из Java, но функция с параметром reified type не может! Я думаю, это причина, по которой не каждый параметр типа встроенной функции автоматически обновляется.
s1m0nw1
Что, если функция представляет собой смесь овеществленных и нерефицированных параметров? Это в любом случае делает невозможным его вызов из Java, почему бы не переопределить все параметры типа автоматически? Почему kotlin нужно явно указывать для всех параметров типа?
Вайраван
1
что, если верхние вызывающие объекты в стеке нуждаются не в json.toKotlinObject <MyJsonType> (), а в json.toKotlinObject <T> () для разных объектов?
семь
1

ПРОСТО

* reified - это разрешение на использование во время компиляции (для доступа к T внутри функции de)

например:

 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)
poiu
источник