Autor
Andrey Gorkovenko
Frontend Engineer at NFON AG

Borrar tipo

Publicado en el grupo Random-ES
¡Hola! Continuamos nuestra serie de lecciones sobre genéricos. Previamente nos hicimos una idea general de qué son y por qué son necesarios. Hoy aprenderemos más sobre algunas de las características de los genéricos y sobre cómo trabajar con ellos. ¡Vamos! Borrado de tipo - 1En la última lección , hablamos sobre la diferencia entre tipos genéricos y tipos sin procesar . Un tipo sin formato es una clase genérica cuyo tipo se ha eliminado.
List list = new ArrayList();
Aquí hay un ejemplo. Aquí no indicamos qué tipo de objetos se colocarán en nuestro List. Si intentamos crearlo Listy agregarle algunos objetos, veremos una advertencia en IDEA:
"Unchecked call to add(E) as a member of raw type of java.util.List".
Pero también hablamos sobre el hecho de que los genéricos solo aparecían en Java 5. Cuando se lanzó esta versión, los programadores ya habían escrito un montón de código usando tipos sin formato, por lo que esta característica del lenguaje no podía dejar de funcionar, y la capacidad de crear tipos sin procesar en Java se conservó. Sin embargo, el problema resultó ser más generalizado. Como sabe, el código Java se convierte a un formato compilado especial llamado bytecode, que luego es ejecutado por la máquina virtual Java. Pero si ponemos información sobre los parámetros de tipo en el código de bytes durante el proceso de conversión, rompería todo el código escrito anteriormente, ¡porque no había parámetros de tipo antes de Java 5! Cuando trabaje con genéricos, hay un concepto muy importante que debe recordar. Se llama borrado de tipos.. Significa que una clase no contiene información sobre un parámetro de tipo. Esta información está disponible solo durante la compilación y se borra (se vuelve inaccesible) antes del tiempo de ejecución. Si intenta poner el tipo de objeto incorrecto en su List<String>, el compilador generará un error. Esto es exactamente lo que los creadores del lenguaje querían lograr cuando crearon genéricos: comprobaciones en tiempo de compilación. Pero cuando todo su código Java se convierte en código de bytes, ya no contiene información sobre los parámetros de tipo. En código de bytes, su List<Cat>lista de gatos no es diferente a List<String>las cadenas. En bytecode, nada dice que catses una lista de Catobjetos. Dicha información se borra durante la compilación; solo el hecho de que tenga una List<Object> catslista terminará en el código de bytes del programa. Veamos cómo funciona esto:
public class TestClass<T> {

   private T value1;
   private T value2;

   public void printValues() {
       System.out.println(value1);
       System.out.println(value2);
   }

   public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
       TestClass<T> result = new TestClass<>();
       result.value1 = (T) o1;
       result.value2 = (T) o2;
       return result;
   }

   public static void main(String[] args) {
       Double d = 22.111;
       String s = "Test String";
       TestClass<Integer> test = createAndAdd2Values(d, s);
       test.printValues();
   }
}
Creamos nuestra propia TestClassclase genérica. Es bastante simple: en realidad es una pequeña "colección" de 2 objetos, que se almacenan inmediatamente cuando se crea el objeto. Tiene 2 Tcampos. Cuando createAndAdd2Values()se ejecuta el método, los dos objetos pasados ​​( Object ay Object bdeben convertirse al Ttipo y luego agregarse al TestClassobjeto. En el main()método, creamos un TestClass<Integer>, es decir, el Integerargumento de tipo reemplaza el Integerparámetro de tipo. También estamos pasando a Doubley a Stringa el createAndAdd2Values()método. ¿Crees que nuestro programa funcionará? Después de todo, especificamos Integercomo el argumento de tipo, ¡pero Stringdefinitivamente no se puede convertir a un Integer! Vamos a ejecutar elmain()método y comprobación. Salida de la consola:
22.111
Test String
¡Eso fue inesperado! ¿Por qué pasó esto? Es el resultado del borrado de tipos. La información sobre el Integerargumento de tipo utilizado para instanciar nuestro TestClass<Integer> testobjeto se borró cuando se compiló el código. El campo se convierte en TestClass<Object> test. Nuestros argumentos Doubley Stringse convirtieron fácilmente en Objectobjetos (¡no se convierten en Integerobjetos como esperábamos!) y se agregaron silenciosamente a TestClass. Aquí hay otro ejemplo simple pero muy revelador de borrado de tipos:
import java.util.ArrayList;
import java.util.List;

public class Main {

   private class Cat {

   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       List<Integer> numbers = new ArrayList<>();
       List<Cat> cats = new ArrayList<>();

       System.out.println(strings.getClass() == numbers.getClass());
       System.out.println(numbers.getClass() == cats.getClass());

   }
}
Salida de la consola:
true
true
Parece que creamos colecciones con tres tipos diferentes de argumentos: String, Integery nuestra propia Catclase. Pero durante la conversión a código de bytes, las tres listas se convierten en List<Object>, por lo que cuando se ejecuta el programa nos dice que estamos usando la misma clase en los tres casos.

Borrado de tipos cuando se trabaja con arreglos y genéricos

Hay un punto muy importante que debe entenderse claramente cuando se trabaja con arreglos y clases genéricas (como List). También debe tenerlo en cuenta al elegir estructuras de datos para su programa. Los genéricos están sujetos a borrado de tipos. La información sobre los parámetros de tipo no está disponible en tiempo de ejecución. Por el contrario, las matrices conocen y pueden usar información sobre su tipo de datos cuando el programa se está ejecutando. Intentar poner un tipo no válido en una matriz provocará que se produzca una excepción:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Salida de la consola:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Debido a que existe una diferencia tan grande entre las matrices y los genéricos, es posible que tengan problemas de compatibilidad. Sobre todo, no puede crear una matriz de objetos genéricos o incluso una matriz parametrizada. ¿Suena un poco confuso? Vamos a ver. Por ejemplo, no puedes hacer nada de esto en Java:
new List<T>[]
new List<String>[]
new T[]
Si intentamos crear una matriz de List<String>objetos, obtenemos un error de compilación que se queja de la creación de una matriz genérica:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       // Compilation error! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
Pero ¿por qué se hace esto? ¿Por qué no se permite la creación de dichas matrices? Todo esto es para proporcionar seguridad de tipo. Si el compilador nos permitiera crear tales arreglos de objetos genéricos, podríamos generarnos muchos problemas. Aquí hay un ejemplo simple del libro de Joshua Bloch "Effective Java":
public static void main(String[] args) {

   List<String>[] stringLists = new List<String>[1];  //  (1)
   List<Integer> intList = Arrays.asList(42, 65, 44);  //  (2)
   Object[] objects = stringLists;  //  (3)
   objects[0] = intList;  //  (4)
   String s = stringLists[0].get(0);  //  (5)
}
Imaginemos que la creación de una matriz como List<String>[] stringListsestá permitida y no generará un error de compilación. Si esto fuera cierto, aquí hay algunas cosas que podríamos hacer: En la línea 1, creamos una matriz de listas: List<String>[] stringLists. Nuestra matriz contiene uno List<String>. En la línea 2, creamos una lista de números: List<Integer>. En la línea 3, asignamos nuestro List<String>[]a una Object[] objectsvariable. El lenguaje Java permite esto: una matriz de Xobjetos puede almacenar Xobjetos y objetos de todas las subclases X. En consecuencia, puede poner cualquier cosa en una Objectmatriz. En la línea 4, reemplazamos el único elemento de la objects()matriz (a List<String>) con a List<Integer>. Por lo tanto, colocamos a List<Integer>en una matriz que solo estaba destinada a almacenarList<String>¡objetos! Encontraremos un error solo cuando ejecutemos la línea 5. ClassCastExceptionSe lanzará una A en tiempo de ejecución. En consecuencia, se agregó a Java una prohibición sobre la creación de dichas matrices. Esto nos permite evitar este tipo de situaciones.

¿Cómo puedo sortear el borrado de tipos?

Bueno, aprendimos sobre el borrado de tipos. ¡Intentemos engañar al sistema! :) Tarea: Tenemos una TestClass<T>clase genérica. Queremos escribir un createNewT()método para esta clase que cree y devuelva un nuevo Tobjeto. Pero esto es imposible, ¿verdad? Toda la información sobre el Ttipo se borra durante la compilación, y en tiempo de ejecución no podemos determinar qué tipo de objeto necesitamos crear. En realidad, hay una forma complicada de hacer esto. Probablemente recuerdes que Java tiene una Classclase. Podemos usarlo para determinar la clase de cualquiera de nuestros objetos:
public class Main2 {

   public static void main(String[] args) {

       Class classInt = Integer.class;
       Class classString = String.class;

       System.out.println(classInt);
       System.out.println(classString);
   }
}
Salida de la consola:
class java.lang.Integer
class java.lang.String
Pero aquí hay un aspecto del que no hemos hablado. ¡En la documentación de Oracle, verá que la clase Class es genérica! Borrado de tipo - 3

https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html

La documentación dice, "T - el tipo de la clase modelada por este objeto Class". Traduciendo esto del lenguaje de la documentación al lenguaje llano, entendemos que la clase del Integer.classobjeto no es solo Class, sino más bien Class<Integer>. El tipo del String.classobjeto no es solo Class, sino Class<String>, etc. Si aún no está claro, intente agregar un parámetro de tipo al ejemplo anterior:
public class Main2 {

   public static void main(String[] args) {

       Class<Integer> classInt = Integer.class;
       // Compilation error!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       // Compilation error!
       Class<Double> classString2 = String.class;
   }
}
¡Y ahora, usando este conocimiento, podemos evitar el borrado de tipos y cumplir nuestra tarea! Intentemos obtener información sobre un parámetro de tipo. Nuestro argumento de tipo será MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("A MySecretClass object was created successfully!");
   }
}
Y así es como usamos nuestra solución en la práctica:
public class TestClass<T> {

   Class<T> typeParameterClass;

   public TestClass(Class<T> typeParameterClass) {
       this.typeParameterClass = typeParameterClass;
   }

   public T createNewT() throws IllegalAccessException, InstantiationException {
       T t = typeParameterClass.newInstance();
       return t;
   }

   public static void main(String[] args) throws InstantiationException, IllegalAccessException {

       TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
       MySecretClass secret = testString.createNewT();

   }
}
Salida de la consola:
A MySecretClass object was created successfully!
Acabamos de pasar el argumento de clase requerido al constructor de nuestra clase genérica:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Esto nos permitió guardar la información sobre el argumento de tipo, evitando que se borrara por completo. Como resultado, pudimos crear unT¡objeto! :) Con eso, la lección de hoy llega a su fin. Siempre debe recordar el borrado de tipos cuando trabaje con genéricos. Esta solución alternativa no parece muy conveniente, pero debe comprender que los genéricos no formaban parte del lenguaje Java cuando se creó. Esta función, que nos ayuda a crear colecciones parametrizadas y detectar errores durante la compilación, se agregó más adelante. En algunos otros lenguajes que incluyeron genéricos de la primera versión, no hay borrado de tipo (por ejemplo, en C#). Por cierto, ¡no hemos terminado de estudiar los genéricos! En la próxima lección, se familiarizará con algunas características más de los genéricos. ¡Por ahora, sería bueno resolver un par de tareas! :)
Comentarios
  • Populares
  • Nuevas
  • Antiguas
Debes iniciar sesión para dejar un comentario
Esta página aún no tiene comentarios