Usa la palabra clave cosificada en Kotlin

David Mbochi Njonge 12 octubre 2023
  1. Genéricos antes de Java 5
  2. Genéricos en Java 5
  3. ¿Qué es el borrado de tipos?
  4. Pasar una clase a una función genérica
  5. Use la palabra clave reificada con una función en línea en Kotlin
  6. Sobrecarga de funciones con la misma entrada usando reificado en Kotlin
  7. Conclusión
Usa la palabra clave cosificada en Kotlin

La palabra clave reificado es un concepto de programación que se usa principalmente al trabajar con genéricos en Kotlin.

Sabemos que el tipo genérico se introdujo en Java 5 para agregar seguridad de tipos a las aplicaciones y evitar conversiones de tipos explícitas.

Una limitación con los genéricos es que no puede acceder a la información de tipo cuando trabaja con funciones genéricas. El compilador se queja de que no puede usar T como un parámetro de tipo reificado, y deberíamos usar una clase en su lugar.

Este problema se debe a la eliminación de tipos durante la compilación. En este tutorial, aprenderemos cómo podemos solucionar este problema usando dos enfoques, incluido pasar la clase del tipo como argumento de la función genérica y usar la palabra clave reificada con una función en línea.

Genéricos antes de Java 5

Antes de Java 5, los genéricos no existían, por lo que no podíamos decir si una implementación de lista era una lista de String, Integer, Objects u otros tipos.

Por esta razón, siempre teníamos que emitir al tipo que queríamos explícitamente. Estas aplicaciones eran propensas a RuntimeException ya que no había validaciones contra conversiones no válidas.

El siguiente ejemplo muestra cómo se implementaron las colecciones antes de Java 5.

import java.util.ArrayList;

public class Main {
  public static void main(String[] args) {
    ArrayList list = new ArrayList();
    list.add("John");
    list.add(3);
    list.add(5);

    String john = (String) list.get(2);
  }
}

Genéricos en Java 5

Los genéricos se introdujeron en Java 5 para resolver este problema. Los genéricos nos permitieron definir un cierto tipo de colección, y si intentamos ingresar un tipo no válido, el compilador muestra una advertencia.

Los genéricos también resolvieron el problema de RuntimeException porque no se requerían conversiones de tipos explícitas para recuperar un elemento de la colección. El siguiente ejemplo muestra cómo se implementaron las colecciones desde Java 5 y versiones anteriores.

import java.util.ArrayList;

public class Main {
  public static void main(String[] args) {
    ArrayList<String> list = new ArrayList<>();
    list.add("John");
    //        list.add(3); // Required type String
    //        list.add(true); // Required type String

    String john = list.get(0);
    System.out.println(john);
  }
}

¿Qué es el borrado de tipos?

El borrado de tipos es la característica que se introdujo en Java 5 para resolver el problema que acabamos de mencionar anteriormente.

Vaya a IntelliJ y seleccione Archivo > Nuevo > Proyecto. Ingrese el nombre del proyecto como reified-in-kotlin en la sección nombre del proyecto.

Seleccione Kotlin en la sección Idioma e Intellij en la sección Build system. Finalmente, presione el botón Crear para generar un nuevo proyecto.

Cree un archivo Main.kt en la carpeta kotlin y copie y pegue el siguiente código en el archivo.

fun <T> showType(t: T){
    println(t);
}

fun main(){
    showType("This is a generic function");
}

Antes de Java 5, no había información de tipo, por lo que el compilador de Java reemplazó la información de tipo con el tipo de objeto base y su conversión de tipo necesaria.

Para ver lo que sucede detrás del capó en Java, ejecute el código y presione Herramientas > Kotlin > Mostrar Kotlin Bytecode. En la ventana que se abre, presione Descompilar para ver el código Java compilado que se muestra a continuación.

public final class MainKt {
  public static final void showType(Object t) {
    System.out.println(t);
  }

  public static final void main() {
    showType("This is a generic function");
  }

  // $FF: synthetic method
  public static void main(String[] var0) {
    main();
  }
}

Tenga en cuenta que el parámetro de tipo que pasamos a los métodos showType() se convierte en un Objeto, por lo que no podemos acceder a la información de tipo. Así es como ocurre el borrado de tipos al trabajar con genéricos.

Pasar una clase a una función genérica

Este es el primer enfoque que podemos usar para evitar el borrado de tipos, aunque no es tan eficiente como usar la palabra clave reificado, que trataremos en la siguiente sección.

Para acceder al tipo genérico eliminado, podemos pasar la clase de tipo genérico como parámetro de la función genérica, como se muestra a continuación.

Comenta el ejemplo anterior y copia y pega el siguiente código en el archivo Main.kt.

fun <T> showType(clazz: Class<T>){
    println(clazz);
}
fun main(){
    showType(String::class.java)
}

Con este enfoque, necesitaremos pasar una clase para el tipo genérico cada vez que creemos una función genérica, que no es lo que queremos.

Ejecute este código y asegúrese de que el código genere lo siguiente.

class java.lang.String

Use la palabra clave reificada con una función en línea en Kotlin

Este es el segundo enfoque que podemos aprovechar y es el método más preferido cuando se accede a información de tipo mediante genéricos. Tenga en cuenta que reificado solo se puede usar con funciones en línea, que están disponibles en Kotlin pero no en Java.

Comenta el ejemplo anterior y copia y pega el siguiente código en el archivo Main.kt.

inline fun < reified T> showType(){
    println(T::class.java);
}
fun main(){
    showType<String>()
}

La función en línea ayuda a la palabra clave reificada a acceder a la información del tipo, copiando el cuerpo de la función en línea en cada lugar donde se ha utilizado.

Ejecute el código anterior y descompílelo siguiendo los mismos pasos que cubrimos anteriormente. El código descompilado se muestra a continuación.

public final class MainKt {
  // $FF: synthetic method
  public static final void showType() {
    int $i$f$showType = 0;
    Intrinsics.reifiedOperationMarker(4, "T");
    Class var1 = Object.class;
    System.out.println(var1);
  }

  public static final void main() {
    int $i$f$showType = false;
    Class var1 = String.class;
    System.out.println(var1);
  }

  // $FF: synthetic method
  public static void main(String[] var0) {
    main();
  }
}

Dado que definimos la función en línea showType() con la palabra clave reificado en el parámetro genérico, el compilador copió el cuerpo de la función y lo reemplazó con el tipo declarado real, como se muestra en el método final llamado main().

El acceso a los tipos declarados nos permite recuperar información de tipo de la función genérica sin pasar la clase como parámetro.

Ejecute el código anterior para verificar que puede acceder a la información de tipo de String, que es el parámetro de tipo que pasamos como argumento.

class java.lang.String

Sobrecarga de funciones con la misma entrada usando reificado en Kotlin

Mientras trabajamos con funciones genéricas normales, no podemos sobrecargar una función con la misma entrada y devolver diferentes tipos, pero podemos lograr esto usando la palabra clave reificada.

Comente el ejemplo anterior y copie y pegue el siguiente código en el archivo después del archivo Main.kt.

inline fun <reified T>computeResult(theNumber: Float): T{
    return when(T::class){
        Float::class -> theNumber as T
        Int::class -> theNumber.toInt() as T
        else -> throw IllegalStateException("")
    }
}
fun main(){
    val intResult: Int = computeResult(123643F);
    println(intResult);
    val floatResult: Float = computeResult(123643F);
    println(floatResult);
}

Detrás de escena, cuando invocamos el método computeResult(), el compilador espera un tipo de retorno Int en la primera llamada y un tipo de retorno Float en la segunda llamada.

El compilador usa esta información para reemplazar el tipo genérico con el archivo esperado mientras copia el cuerpo de la función.

No es una buena práctica usar funciones grandes mientras se usa reificado, ya que introduce problemas de rendimiento en nuestras aplicaciones. Asegúrese de que su función en línea sea pequeña.

Conclusión

Entonces, hemos aprendido a usar la palabra clave reificado con funciones en línea para acceder a la información de tipo en Kotlin.

También hemos aprendido sobre el borrado de tipos, que es la causa de la eliminación en tiempo de ejecución de la información de tipos. Por último, cubrimos cómo sobrecargar las funciones en línea usando reificado con la misma entrada para devolver diferentes tipos.

David Mbochi Njonge avatar David Mbochi Njonge avatar

David is a back end developer with a major in computer science. He loves to solve problems using technology, learning new things, and making new friends. David is currently a technical writer who enjoys making hard concepts easier for other developers to understand and his work has been published on multiple sites.

LinkedIn GitHub