JavaRush /Blog Java /Random-ES /Tipos de borrado

Tipos de borrado

Publicado en el grupo Random-ES
¡Hola! Continuamos nuestra serie de conferencias sobre genéricos. Anteriormente , descubrimos en términos generales qué es y por qué es necesario. Hoy hablaremos sobre algunas de las características de los genéricos y veremos algunos inconvenientes al trabajar con ellos. ¡Ir! Tipos de borrado - 1En la última conferencia, hablamos sobre la diferencia entre tipos genéricos y tipos sin procesar . En caso de que lo hayas olvidado, Raw Type es una clase genérica de la que se ha eliminado su tipo.
List list = new ArrayList();
He aquí un ejemplo. Aquí no especificamos qué tipo de objetos se colocarán en nuestro List. Si intentamos crear uno 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 aparecieron solo en la versión del lenguaje Java 5. Cuando se lanzó, los programadores habían escrito mucho código usando Raw Types, y para que no dejara de funcionar, la capacidad de Se conservó la creación y el trabajo con tipos sin formato en Java. Sin embargo, este problema resultó ser mucho más amplio. El código Java, como usted sabe, se convierte en un código de bytes especial, que luego es ejecutado por la máquina virtual Java. Y si durante el proceso de traducción colocamos información sobre los tipos de parámetros en el código de bytes, se rompería todo el código escrito anteriormente, porque antes de Java 5 no existían tipos de parámetros. Al trabajar con genéricos, hay una característica muy importante que debes recordar. Se llama borrado de tipo. Su esencia radica en el hecho de que dentro de la clase no se almacena ninguna información sobre su tipo de parámetro. Esta información está disponible sólo en la etapa de compilación y se borra (se vuelve inaccesible) en tiempo de ejecución. Si intenta colocar un objeto del tipo incorrecto en su archivo List<String>, el compilador arrojará un error. Esto es precisamente lo que lograron los creadores del lenguaje al crear genéricos: controles en la etapa de compilación. Pero cuando todo el código Java que escriba se convierta en código de bytes, no habrá información sobre los tipos de parámetros. Dentro del código de bytes, su lista de List<Cat>gatos no diferirá de List<String>las cadenas. Nada en el código de bytes dirá que catsse trata de una lista de objetos Cat. La información sobre esto se borrará durante la compilación, y solo la información que tenga en una lista determinada en su programa entrará en el código de bytes List<Object> cats. Vamos a ver cómo funciona:
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 clase genérica TestClass. Es bastante simple: esencialmente es una pequeña “colección” de 2 objetos, que se colocan allí inmediatamente cuando se crea el objeto. Tiene 2 objetos como campos T. Cuando se ejecuta el método, createAndAdd2Values()los dos objetos pasados ​​deben convertirse Object aa Object bnuestro tipo T, después de lo cual se agregarán al objeto TestClass. En el método main()que creamos TestClass<Integer>, es decir, en la calidad Tque tendremos Integer. Pero al mismo tiempo createAndAdd2Values()le pasamos un número Doubley un objeto al método String. ¿Crees que nuestro programa funcionará? Después de todo, especificamos como tipo de parámetro Integer, ¡pero Stringciertamente no se puede convertir a Integer! Ejecutemos el método main()y verifiquemos. Salida de la consola: 22.111 Cadena de prueba ¡Resultado inesperado! ¿Por qué pasó esto? Precisamente por el borrado de tipos. Durante la compilación del código, se borró la información sobre el tipo de parámetro Integerde nuestro objeto . TestClass<Integer> testSe convirtió en TestClass<Object> test. Nuestros parámetros se transformaron sin ningún problema Double( ¡y no en , como esperábamos!) y se agregaron silenciosamente a . Aquí hay otro ejemplo simple pero muy ilustrativo de borrado de tipos: StringObjectIntegerTestClass
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: verdadero verdadero Parecería que hemos creado colecciones con tres tipos de parámetros diferentes: String, Integery la clase que creamos Cat. Pero durante la conversión a código de bytes, las tres listas se convirtieron en List<Object>, por lo que cuando se ejecuta, el programa nos dice que en los tres casos estamos usando la misma clase.

Borrado de tipos al trabajar con matrices y genéricos

Hay un punto muy importante que debe entenderse claramente cuando se trabaja con matrices y genéricos (por ejemplo, List). También vale la pena considerarlo al elegir una estructura de datos para su programa. Los genéricos están sujetos a borrado de tipos. La información sobre el tipo de parámetro no está disponible durante la ejecución del programa. Por el contrario, las matrices conocen y pueden utilizar información sobre su tipo de datos durante la ejecución del programa. Intentar poner un valor del tipo incorrecto en una matriz generará una excepción:
public class Main2 {

   public static void main(String[] args) {

       Object x[] = new String[3];
       x[0] = new Integer(222);
   }
}
Salida de consola:

Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
Debido a que existe una diferencia tan grande entre matrices y genéricos, es posible que tengan problemas de compatibilidad. En primer lugar, no se puede crear una matriz de objetos genéricos, ni siquiera una matriz escrita. ¿Suena un poco confuso? Miremos más de cerca. Por ejemplo, no puedes hacer nada de esto en Java:
new List<T>[]
new List<String>[]
new T[]
Si intentamos crear una matriz de listas List<String>, obtenemos un error de compilación de creación de matriz genérica:
import java.util.List;

public class Main2 {

   public static void main(String[] args) {

       //ошибка компиляции! Generic array creation
       List<String>[] stringLists = new List<String>[1];
   }
}
¿Pero por qué se hizo esto? ¿Por qué está prohibida la creación de este tipo de matrices? Todo esto es para garantizar la seguridad tipográfica. Si el compilador nos permitiera crear tales matrices a partir de objetos genéricos, podríamos meternos en 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 List<String>[] stringListsse permitiría la creación de matrices y el compilador no se quejaría. Esto es lo que podríamos hacer en este caso: En la línea 1, creamos una matriz de hojas 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 nuestra matriz List<String>[]a una variable Object[] objects. El lenguaje Java le permite hacer esto: Xpuede colocar tanto objetos Xcomo objetos de todas las clases secundarias en una matriz de objetos Х. En consecuencia, Objectspuedes poner cualquier cosa en la matriz. En la línea 4 reemplazamos el elemento único de la matriz objects (List<String>)con una lista List<Integer>. Como resultado, colocamos List<Integer>en nuestra matriz, que estaba destinada únicamente a almacenar List<String>! Encontraremos un error solo cuando el código llegue a la línea 5. Se generará una excepción durante la ejecución del programa ClassCastException. Por lo tanto, se introdujo la prohibición de crear tales matrices en el lenguaje Java, lo que nos permite evitar tales situaciones.

¿Cómo puedo evitar el borrado de tipos?

Bueno, hemos aprendido sobre el borrado de tipos. ¡Intentemos engañar al sistema! :) Tarea: Tenemos una clase genérica TestClass<T>. Necesitamos crear un método createNewT()que creará y devolverá un nuevo objeto de tipo Т. Pero esto es imposible de hacer, ¿verdad? Toda la información sobre el tipo Тse borrará durante la compilación y, mientras el programa se esté ejecutando, no podremos saber qué tipo de objeto necesitamos crear. De hecho, hay una forma complicada. Probablemente recuerdes que hay una clase en Java Class. Usándolo, podemos obtener 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 consola:

class java.lang.Integer
class java.lang.String
Pero aquí hay una característica de la que no hablamos. ¡En la documentación de Oracle verá que Class es una clase genérica! Tipos de borrado - 3La documentación dice: "T es el tipo de clase modelada por este objeto Clase". Si traducimos esto del lenguaje de documentación al lenguaje humano, significa que la clase de un objeto Integer.classno es solo Class, sino Class<Integer>. El tipo de objeto string.classno es solo Class, 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;
       //ошибка компиляции!
       Class<String> classInt2 = Integer.class;


       Class<String> classString = String.class;
       //ошибка компиляции!
       Class<Double> classString2 = String.class;
   }
}
¡Y ahora, usando este conocimiento, podemos evitar el borrado de tipos y resolver nuestro problema! Intentemos obtener información sobre el tipo de parámetro. Su papel lo desempeñará la clase MySecretClass:
public class MySecretClass {

   public MySecretClass() {

       System.out.println("Объект секретного класса успешно создан!");
   }
}
Así es como utilizamos 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 consola:

Объект секретного класса успешно создан!
Simplemente pasamos el parámetro de clase requerido al constructor de nuestra clase genérica:
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
Gracias a esto, guardamos información sobre el tipo de parámetro y la protegimos para que no se borrara. Como resultado, pudimos crear un objeto T. :) Esto concluye la conferencia de hoy. El borrado de tipos siempre es algo a tener en cuenta cuando se trabaja con genéricos. Esto no parece muy conveniente, pero es necesario comprender que los genéricos no formaban parte del lenguaje Java cuando se creó. Esta es una característica agregada posteriormente que nos ayuda a crear colecciones escritas y detectar errores en la etapa de compilación. Algunos otros lenguajes donde los genéricos han existido desde la versión 1 no tienen borrado de tipo (por ejemplo, C#). Sin embargo, ¡no hemos terminado de estudiar los genéricos! En la próxima conferencia conocerá algunas características más para trabajar con ellos. Mientras tanto, ¡sería bueno resolver un par de problemas! :)
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION