CodeGym /Blogue Java /Random-PT /Usando varargs ao trabalhar com genéricos
John Squirrels
Nível 41
San Francisco

Usando varargs ao trabalhar com genéricos

Publicado no grupo Random-PT
Oi! Na lição de hoje, continuaremos a estudar os genéricos. Acontece que este é um tópico extenso, mas não há como evitá-lo — é uma parte extremamente importante da linguagem :) Ao estudar a documentação do Oracle sobre genéricos ou ler tutoriais on-line, você encontrará os termos tipos não reificáveis ​​e tipos reificáveis . Um tipo reificável é um tipo para o qual as informações estão totalmente disponíveis em tempo de execução. Em Java, esses tipos incluem primitivos, tipos brutos e tipos não genéricos. Em contraste, tipos não reificáveis ​​são tipos cujas informações são apagadas e ficam inacessíveis em tempo de execução. Acontece que esses são genéricos - List<String>, List<Integer>, etc.

A propósito, você se lembra o que é varargs?

Caso você tenha esquecido, este é um argumento de comprimento variável. Eles são úteis em situações onde não sabemos quantos argumentos podem ser passados ​​para nosso método. Por exemplo, se tivermos uma classe de calculadora que tenha um summétodo. O sum()método pode receber 2 números, ou 3, ou 5, ou quantos você quiser. Seria muito estranho sobrecarregar o sum()método para cada número possível de argumentos. Em vez disso, podemos fazer isso:

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));
   }
}
Saída do console:

15
11
Isso nos mostra que existem alguns recursos importantes ao usar varargs em combinação com genéricos. Vejamos o seguinte 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")
       );
   }
}
O addAll()método recebe como entrada a List<E>e qualquer número de Eobjetos e, em seguida, adiciona todos esses objetos à lista. No main()método, chamamos nosso addAll()método duas vezes. No primeiro caso, adicionamos duas strings comuns ao arquivo List. Tudo está em ordem aqui. No segundo caso, adicionamos dois Pair<String, String>objetos ao arquivo List. Mas aqui recebemos inesperadamente um aviso:

Unchecked generics array creation for varargs parameter
O que isso significa? Por que recebemos um aviso e por que há menção a um array? Afinal, nosso código não tem um array! Comecemos pelo segundo caso. O aviso menciona uma matriz porque o compilador converte o argumento de comprimento variável (varargs) em uma matriz. Em outras palavras, a assinatura do nosso addAll()método é:

public static <E> void addAll(List<E> list, E... array)
Na verdade, é assim:

public static <E> void addAll(List<E> list, E[] array)
Ou seja, no main()método, o compilador converte nosso código nisso:

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") 
        } 
   ); 
}
Uma Stringmatriz está bem. Mas uma Pair<String, String>matriz não é. O problema é que Pair<String, String>é um tipo não reificável. Durante a compilação, todas as informações sobre argumentos de tipo (<String, String>) são apagadas. A criação de arrays de um tipo não reificável não é permitida em Java . Você pode ver isso se tentar criar manualmente um array Pair<String, String>

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
A razão é óbvia: tipo de segurança. Como você deve se lembrar, ao criar um array, você definitivamente precisa especificar quais objetos (ou primitivos) o array irá armazenar.

int array[] = new int[10];
Em uma de nossas lições anteriores, examinamos o apagamento de tipos em detalhes. Nesse caso, o apagamento de tipo faz com que percamos as informações que os Pairobjetos armazenam <String, String>aos pares. Criar a matriz seria inseguro. Ao usar métodos que envolvem varargs e genéricos, certifique-se de lembrar sobre o apagamento de tipo e como ele funciona. Se você está absolutamente certo sobre o código que escreveu e sabe que não causará nenhum problema, pode desativar os avisos relacionados a varargs usando as @SafeVarargsanotações.

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

   for (E element : array) {
       list.add(element);
   }
}
Se você adicionar essa anotação ao seu método, o aviso que encontramos anteriormente não aparecerá. Outro problema que pode ocorrer ao usar varargs com genéricos é a poluição da pilha. Usando varargs ao trabalhar com genéricos - 3A poluição heap pode acontecer na seguinte situação:

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));
   }
}
Saída do console:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Em termos simples, a poluição do heap é quando os objetos do tipo Adeveriam estar no heap, mas os objetos do tipo Bacabam lá devido a erros relacionados à segurança do tipo. No nosso exemplo, é exatamente isso que acontece. Primeiro, criamos a numbersvariável bruta e atribuímos uma coleção genérica ( ArrayList<Number>) a ela. Em seguida, adicionamos o número 1à coleção.

List<String> strings = numbers;
Nesta linha, o compilador tentou nos avisar sobre possíveis erros emitindo o aviso " Atribuição não verificada... ", mas nós o ignoramos. Acabamos com uma variável genérica de tipo List<String>que aponta para uma coleção genérica de tipo ArrayList<Number>. Claramente, esta situação pode levar a problemas! E assim acontece. Usando nossa nova variável, adicionamos uma string à coleção. Agora temos pilha de poluição — adicionamos um número e depois uma string à coleção parametrizada. O compilador nos avisou, mas ignoramos seu aviso. Como resultado, obtemos ClassCastExceptionapenas enquanto o programa está em execução. Então, o que isso tem a ver com varargs? O uso de varargs com genéricos pode facilmente levar à poluição de pilhas. Aqui está um exemplo simples:

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);
   }
}
O que está acontecendo aqui? Devido ao apagamento de tipo, nosso argumento de comprimento variável

List<String>...stringsLists
torna-se um array de listas, ou seja List[], de objetos de um tipo desconhecido (não se esqueça que varargs se transforma em um array regular durante a compilação). Por causa disso, podemos atribuí-lo facilmente à Object[] arrayvariável na primeira linha do método — o tipo dos objetos em nossas listas foi apagado! E agora temos uma Object[]variável, à qual podemos adicionar qualquer coisa, já que todos os objetos em Java herdam Object! A princípio, temos apenas um array de listas de strings. Mas, graças ao apagamento de tipos e ao uso de varargs, podemos adicionar facilmente uma lista de números, o que fazemos. Como resultado, poluímos a pilha misturando objetos de diferentes tipos. O resultado será outro ClassCastExceptionquando tentarmos ler uma string do array. Saída do console:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Essas consequências inesperadas podem ser causadas pelo uso de varargs, um mecanismo aparentemente simples :) E com isso, a aula de hoje chega ao fim. Não se esqueça de resolver algumas tarefas e, se tiver tempo e energia, estude algumas leituras adicionais. " Java eficaz " não lê sozinho! :) Até a próxima vez!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION