generics - type - new in kotlin
¿Cómo funciona la palabra clave reified en Kotlin? (1)
TL; DR: ¿Qué es
reified
bueno para
fun <T> myGenericFun(c: Class<T>)
En el cuerpo de una función genérica como
myGenericFun
, no puede acceder al tipo
T
porque
solo está disponible en tiempo de compilación
pero se
erased
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
.
Sin embargo, si crea una función en
inline
con una
T
reificada
, se puede acceder al tipo de
T
incluso en tiempo de ejecución y, por lo tanto, no necesita pasar la
Class<T>
adicionalmente.
Puede trabajar con
T
como si fuera una clase normal, por ejemplo, es posible que desee comprobar si una variable es una
instancia de
T
, lo que puede hacer fácilmente:
myVar is T
Tal función en
inline
con
reified
type
T
ve de la siguiente manera:
inline fun <reified T> myGenericFun()
Cómo funciona
reified
Solo puede usar
reified
en combinación con una
función en
inline
.
Dicha 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, llamadas como
myVar is T
convierten en
myVar is String
(si el argumento de tipo fuera
String
) en el
myVar is String
y en tiempo de ejecución.
Ejemplo
Echemos un vistazo a un ejemplo que muestra cuán útil puede ser la
reified
.
Queremos crear una función de extensión para
String
llamada
toKotlinObject
que intente convertir una cadena JSON en un objeto Kotlin simple con un tipo especificado por el tipo genérico
T
la función.
Podemos usar
com.fasterxml.jackson.module.kotlin
para esto 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 método
readValue
toma un tipo en el que se supone que analiza el
JsonObject
.
Si intentamos obtener la
Class
del parámetro de tipo
T
, el compilador se queja:
"No se puede usar ''T'' como parámetro de tipo reificado. Utilice una clase en su lugar".
b) Solución alternativa con parámetro de
Class
explícito
fun <T: Any> String.toKotlinObject(c: KClass<T>): T {
val mapper = jacksonObjectMapper()
return mapper.readValue(this, c.java)
}
Como solución alternativa, la
Class
de
T
se puede convertir en un parámetro de método, que luego se usa como argumento para
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) El camino de Kotlin:
reified
El uso de una función en
inline
con el parámetro de tipo
reified
T
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 la
Class
de
T
adicionalmente,
T
puede usarse como si fuera 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 tipo
reified
no
se
puede llamar desde el
código
Java
.
Estoy tratando de comprender el propósito de la palabra clave
reified
, aparentemente
nos permite reflexionar sobre los genéricos
.
Sin embargo, cuando lo dejo afuera, funciona igual de bien. ¿Alguien quiere explicar cuándo esto hace una diferencia real?