¿Cómo funciona la palabra clave reified en Kotlin?

144

Estoy tratando de entender el propósito de la reifiedpalabra clave, aparentemente nos permite reflexionar sobre los genéricos .

Sin embargo, cuando lo dejo fuera, funciona igual de bien. ¿Alguien quiere explicar cuándo esto hace una diferencia real ?

hl3mukkel
fuente
¿Estás seguro de saber qué es la reflexión? Además, ¿escuchaste sobre el tipo de borrado?
Mibac
3
Los parámetros de tipo genérico se borran en tiempo de ejecución, lea sobre el borrado de tipo si aún no lo ha hecho. Los parámetros de tipo reificado en funciones en línea no solo incorporan el cuerpo del método, sino también el parámetro de tipo genérico que le permite hacer cosas como T :: class.java (que no puede hacer con los tipos genéricos normales). Poniendo como comentario porque no tengo tiempo para dar una respuesta completa en este momento ...
F. George
Permite acceder al tipo genérico concreto de una función sin depender de la reflexión y sin tener que pasar el tipo como argumento.
BladeCoder

Respuestas:

368

TL; DR: ¿Qué es reifiedbueno para

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

En el cuerpo de una función genérica como myGenericFun, no puede acceder al tipo Tporque solo está disponible en tiempo de compilación pero se borra en tiempo de ejecución. Por lo tanto, si desea utilizar el tipo genérico como una clase normal en el cuerpo de la función, debe pasar explícitamente la clase como parámetro como se muestra en myGenericFun.

Si creas un inline función con una reified T , Tse puede acceder al tipo de incluso en tiempo de ejecución y, por lo tanto, no necesita pasarlo Class<T>adicionalmente. Puede trabajar con Tcomo si fuera una clase normal, por ejemplo, es posible que desee comprobar si una variable es un ejemplo de T que se puede hacer fácilmente a continuación: myVar is T.

Tal inlinefunción con reifiedtipo se Tve de la siguiente manera:

inline fun <reified T> myGenericFun()

Como reifiedfunciona

Solo se puede usar reifieden combinación con una inlinefunción . Tal función hace que el compilador copie el código de bytes de la función en cada lugar donde se está utilizando la función (la función está "en línea"). Cuando llama a una función en línea con tipo reified, el compilador conoce el tipo real utilizado como argumento de tipo y modifica el bytecode generado para usar la clase correspondiente directamente. Por lo tanto, las llamadas se myVar is Tconvierten en myVar is String(si el argumento de tipo fuera String) en el código de bytes y en tiempo de ejecución.


Ejemplo

Echemos un vistazo a un ejemplo que muestra cuán útil reified puede ser. Queremos crear una función de extensión para Stringllamadas toKotlinObjectque intente convertir una cadena JSON en un objeto Kotlin simple con un tipo especificado por el tipo genérico de la función T. Podemos usar com.fasterxml.jackson.module.kotlinesto y el primer enfoque es el siguiente:

a) Primer enfoque sin tipo reificado

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

El readValuemétodo toma un tipo que se supone que debe analizar JsonObject. Si tratamos de obtener elClass parámetro type T, el compilador se queja: "No se puede usar 'T' como parámetro de tipo reified. En su lugar, use una clase".

b) Solución alternativa con explícito Class parámetro

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

Como solución alternativa, la ClassdeT puede hacerse un parámetro de método, que luego se usa como un argumento a readValue. Esto funciona y es un patrón común en el código genérico de Java. Se puede llamar de la siguiente manera:

data class MyJsonType(val name: String)

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

c) La forma de Kotlin: reified

Usar una inlinefunción con reifiedparámetro de tipoT hace posible implementar la función de manera diferente:

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

No hay necesidad de tomar el Classde Tadicionalmente,T se puede utilizar como si se tratara de una clase ordinaria. Para el cliente, el código se ve así:

json.toKotlinObject<MyJsonType>()

Nota importante: trabajar con Java

Una función en línea con reifiedtipo no se puede llamar desde el código Java .

s1m0nw1
fuente
66
¡Gracias por su respuesta integral! Eso realmente tiene sentido. Solo una cosa que me pregunto, ¿por qué es necesario reified si la función se está incorporando de todos modos? ¿Dejaría el borrado de tipo e alinearía la función de todos modos? Esto me parece un desperdicio, si alineas la función, también podrías incluir el tipo que se está utilizando o ¿veo algo mal aquí?
hl3mukkel
66
Gracias por sus comentarios, en realidad me olvido de mencionar algo que podría darle la respuesta: ¡se puede llamar a una función en línea normal desde Java pero no se puede llamar a una con un parámetro de tipo reified! Creo que esta es una razón por la cual no todos los parámetros de tipo de una función en línea se reifica automáticamente.
s1m0nw1
¿Qué sucede si la función es una mezcla de parámetros reificados y no reificados? Eso hace que no sea elegible para ser llamado desde Java de todos modos, ¿por qué no reificar todos los parámetros de tipo automáticamente? ¿Por qué Kotlin necesita haber reificado específicamente para todos los parámetros de tipo explícitamente?
Vairavan
1
¿Qué pasa si los llamadores superiores en la pila no necesitan json.toKotlinObject <MyJsonType> (), sino json.toKotlinObject <T> () para diferentes objetos?
siete
1

SENCILLO

* reified es dar permiso para usar en tiempo de compilación (para acceder a T dentro de la función)

p.ej:

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

    val gson = Gson()

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

}

usando como:

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