CodeGym /Blog Java /Random-ES /Usar varargs cuando se trabaja con genéricos
Autor
John Selawsky
Senior Java Developer and Tutor at LearningTree

Usar varargs cuando se trabaja con genéricos

Publicado en el grupo Random-ES
¡Hola! En la lección de hoy, continuaremos estudiando los genéricos. Da la casualidad de que este es un gran tema, pero no se puede evitar: es una parte extremadamente importante del lenguaje :) Cuando estudie la documentación de Oracle sobre genéricos o lea tutoriales en línea, se encontrará con los términos tipos no verificables y tipos verificables . Un tipo verificable es un tipo para el cual la información está totalmente disponible en tiempo de ejecución. En Java, estos tipos incluyen primitivos, tipos sin procesar y tipos no genéricos. Por el contrario, los tipos no verificables son tipos cuya información se borra y se vuelve inaccesible en tiempo de ejecución. Da la casualidad de que estos son genéricos: List<String>, List<Integer>, etc.

Por cierto, ¿recuerdas qué es varargs?

En caso de que lo hayas olvidado, este es un argumento de longitud variable. Son útiles en situaciones en las que no sabemos cuántos argumentos se pueden pasar a nuestro método. Por ejemplo, si tenemos una clase de calculadora que tiene un summétodo. El sum()método puede recibir 2 números, o 3, o 5, o tantos como quieras. Sería muy extraño sobrecargar el sum()método para cada número posible de argumentos. En su lugar, podemos hacer esto:

public class SimpleCalculator {

   public static int sum(int...numbers) {

       int result = 0;

       for(int i : numbers) {

           result += i;
       }

       return result;
   }

   public static void main(String[] args) {

       System.out.println(sum(1,2,3,4,5));
       System.out.println(sum(2,9));
   }
}
Salida de la consola:

15
11
Esto nos muestra que hay algunas características importantes cuando se usan varargs en combinación con genéricos. Veamos el siguiente código:

import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static <E> void addAll(List<E> list, E... array) {

       for (E element : array) {
           list.add(element);
       }
   }

   public static void main(String[] args) {
       addAll(new ArrayList<String>(), // This is okay
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // but here we get a warning
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
El addAll()método toma como entrada a List<E>y cualquier cantidad de Eobjetos, y luego agrega todos estos objetos a la lista. En el main()método, llamamos a nuestro addAll()método dos veces. En el primer caso, agregamos dos cadenas ordinarias al List. Todo está en orden aquí. En el segundo caso, agregamos dos Pair<String, String>objetos al archivo List. Pero aquí inesperadamente recibimos una advertencia:

Unchecked generics array creation for varargs parameter
¿Qué significa eso? ¿Por qué recibimos una advertencia y por qué se menciona un array? Después de todo, nuestro código no tiene un array! Comencemos con el segundo caso. La advertencia menciona una matriz porque el compilador convierte el argumento de longitud variable (varargs) en una matriz. En otras palabras, la firma de nuestro addAll()método es:

public static <E> void addAll(List<E> list, E... array)
En realidad se ve así:

public static <E> void addAll(List<E> list, E[] array)
Es decir, en el main()método, el compilador convierte nuestro código en esto:

public static void main(String[] args) { 
   addAll(new ArrayList<String>(), 
      new String[] { 
        "Leonardo da Vinci", 
        "Vasco de Gama" 
      } 
   ); 
   addAll(new ArrayList<Pair<String,String>>(),
        new Pair<String,String>[] { 
            new Pair<String,String>("Leonardo","da Vinci"), 
            new Pair<String,String>("Vasco","de Gama") 
        } 
   ); 
}
Una Stringmatriz está bien. Pero una Pair<String, String>matriz no lo es. El problema es que Pair<String, String>es un tipo no verificable. Durante la compilación, se borra toda la información sobre los argumentos de tipo (<String, String>). Java no permite la creación de matrices de un tipo no verificable . Puede ver esto si intenta crear manualmente una matriz Pair<String, String>

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
La razón es obvia: tipo de seguridad. Como recordará, al crear una matriz, definitivamente necesita especificar qué objetos (o primitivos) almacenará la matriz.

int array[] = new int[10];
En una de nuestras lecciones anteriores, examinamos el borrado de tipos en detalle. En este caso, el borrado de tipos hace que perdamos la información que los Pairobjetos almacenan <String, String>pares. Crear la matriz no sería seguro. Cuando utilice métodos que involucren varargs y genéricos, asegúrese de recordar el borrado de tipos y cómo funciona. Si está absolutamente seguro del código que ha escrito y sabe que no causará ningún problema, puede desactivar las advertencias relacionadas con varargs utilizando las @SafeVarargsanotaciones.

@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
Si agrega esta anotación a su método, la advertencia que encontramos anteriormente no aparecerá. Otro problema que puede ocurrir cuando se usan varargs con genéricos es la contaminación del montón. Uso de varargs cuando se trabaja con genéricos - 3La contaminación por pilas puede ocurrir en la siguiente situación:

import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> polluteHeap() {
       List numbers = new ArrayList<Number>();
       numbers.add(1);
       List<String> strings = numbers;
       strings.add("");
       return strings;
   }

   public static void main(String[] args) {

       List<String> stringsWithHeapPollution = polluteHeap();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
Salida de la consola:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
En términos simples, la contaminación del montón es cuando los objetos de tipo Adeberían estar en el montón, pero los objetos de tipo Bterminan allí debido a errores relacionados con la seguridad de tipo. En nuestro ejemplo, esto es precisamente lo que sucede. Primero, creamos la numbersvariable sin formato y ArrayList<Number>le asignamos una colección genérica ( ). Luego agregamos el número 1a la colección.

List<String> strings = numbers;
En esta línea, el compilador intentó advertirnos de posibles errores emitiendo la advertencia " Asignación no verificada... ", pero la ignoramos. Terminamos con una variable genérica de tipo List<String>que apunta a una colección genérica de tipo ArrayList<Number>. ¡Claramente, esta situación puede causar problemas! Y así es. Usando nuestra nueva variable, agregamos una cadena a la colección. Ahora tenemos contaminación del montón: agregamos un número y luego una cadena a la colección parametrizada. El compilador nos advirtió, pero ignoramos su advertencia. Como resultado, obtenemos un ClassCastExceptionsolo mientras el programa se está ejecutando. Entonces, ¿qué tiene esto que ver con varargs? El uso de varargs con genéricos puede conducir fácilmente a la contaminación del montón. Aquí hay un ejemplo simple:

import java.util.Arrays;
import java.util.List;

public class Main {

   static void polluteHeap(List<String>... stringsLists) {
       Object[] array = stringsLists;
       List<Integer> numbersList = Arrays.asList(66,22,44,12);

       array[0] = numbersList;
       String str = stringsLists[0].get(0);
   }

   public static void main(String[] args) {

       List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
       List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");

       polluteHeap(cars1, cars2);
   }
}
¿Que está pasando aqui? Debido al borrado de tipo, nuestro argumento de longitud variable

List<String>...stringsLists
se convierte en una matriz de listas, es decir List[], de objetos de un tipo desconocido (no olvide que varargs se convierte en una matriz regular durante la compilación). Debido a esto, podemos asignarlo fácilmente a la Object[] arrayvariable en la primera línea del método: ¡se ha borrado el tipo de los objetos en nuestras listas! Y ahora tenemos una Object[]variable, a la que podemos agregar cualquier cosa, ¡ya que todos los objetos en Java heredan Object! Al principio, solo tenemos una matriz de listas de cadenas. Pero gracias al borrado de tipo y nuestro uso de varargs, podemos agregar fácilmente una lista de números, lo cual hacemos. Como resultado, contaminamos el montón al mezclar objetos de diferentes tipos. El resultado será otro ClassCastExceptioncuando intentemos leer una cadena de la matriz. Salida de la consola:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Tales consecuencias inesperadas pueden ser causadas por el uso de varargs, un mecanismo aparentemente simple :) Y con eso, la lección de hoy llega a su fin. No olvides resolver un par de tareas y, si tienes tiempo y energía, estudia alguna lectura adicional. ¡ " Efective Java " no se lee solo! :) ¡Hasta la proxima vez!
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION