JavaRush /Blogue Java /Random-PT /Usando varargs ao trabalhar com genéricos

Usando varargs ao trabalhar com genéricos

Publicado no grupo Random-PT
Olá! Na lição de hoje continuaremos a estudar genéricos. Acontece que este é um grande tópico, mas não há para onde ir - esta é uma parte extremamente importante da linguagem :) Ao estudar a documentação genérica da Oracle ou ler guias na Internet, você encontrará os termos Tipos Não-Reificáveis ​​e Tipos Reificáveis . Que tipo de palavra é “Reificável”? Mesmo que tudo esteja bem com o inglês, é improvável que você o conheça. Vamos tentar traduzir! Usando varargs ao trabalhar com genéricos - 2
*obrigado Google, você ajudou muito ---*
Um tipo reificável é um tipo cujas informações estão totalmente disponíveis em tempo de execução. Na linguagem Java, incluem primitivos, tipos brutos e tipos não genéricos. Em contraste, os Tipos Não-Reificáveis ​​são tipos cujas informações são apagadas e ficam indisponíveis em tempo de execução. Estes são apenas genéricos - List<String> , List<Integer> , etc.

A propósito, você lembra o que são varargs ?

Caso você tenha esquecido, estes são argumentos de comprimento variável. Eles são úteis em situações onde não sabemos exatamente quantos argumentos podem ser passados ​​para o nosso método. Por exemplo, se tivermos uma classe de calculadora e ela tiver um método sum. sum()Você pode passar 2 números, 3, 5 ou quantos quiser para o método . Seria muito estranho sobrecarregar o método todas as vezes sum()para levar em conta todas as opções possíveis. 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
Portanto, quando usado varargsem combinação com genéricos, existem algumas características importantes. Vejamos este 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>(),  //  здесь все нормально
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // а здесь мы получаем предупреждение
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
O método pega uma lista e qualquer número de objetos addAll()como entrada e depois adiciona todos esses objetos à lista. No método chamamos nosso método duas vezes . Na primeira vez, adicionamos duas linhas regulares. Está tudo bem aqui. Na segunda vez, adicionamos dois objetos . E aqui de repente recebemos um aviso: List<E>Emain()addAll()ListListPair<String, String>

Unchecked generics array creation for varargs parameter
O que isso significa? Por que recebemos um aviso e o que isso tem a ver com isso array? Array- este é um array e não há arrays em nosso código! Vamos começar com o segundo. O aviso menciona um array porque o compilador converte argumentos de comprimento variável (varargs) em um array. Em outras palavras, a assinatura do nosso método é addAll():
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 método main(), o compilador irá converter 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")
        }
   );
}
Está tudo bem com o array String. Mas com um array Pair<String, String>- não. O fato é que Pair<String, String>este é um Tipo Não-Reificável. Durante a compilação, todas as informações sobre os tipos de parâmetros (<String, String>) serão apagadas. A criação de arrays do tipo não reificável não é permitida em Java . Você pode verificar isso se tentar criar manualmente um array Pair<String, String>
public static void main(String[] args) {

   //  ошибка компиляции! Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
A razão é óbvia - tipo segurança. Como você lembra, ao criar um array, você deve indicar quais objetos (ou primitivos) esse array irá armazenar.
int array[] = new int[10];
Em uma das lições anteriores, examinamos detalhadamente o mecanismo de apagamento de tipos. Então, neste caso, como resultado do apagamento dos tipos, perdemos a informação de que Pairos pares estavam armazenados em nossos objetos <String, String>. Criar um array não será seguro. Ao usar métodos com varargse genéricos, lembre-se do apagamento de tipo e exatamente como ele funciona. Se você está absolutamente confiante no código que escreveu e sabe que ele não causará problemas, você pode desabilitar os varargsavisos associados a ele usando uma anotação@SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
Se você adicionar esta anotação ao seu método, o aviso que encontramos anteriormente não aparecerá. Outro possível problema ao usar varargsgenéricos juntos é a poluição acumulada. Usando varargs ao trabalhar com genéricos - 4A contaminação pode ocorrer nas seguintes situações:
import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> makeHeapPollution() {
       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 = makeHeapPollution();

       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, poluição por heap é uma situação em que objetos do tipo 1 deveriam estar no heap А, mas objetos do tipo acabam lá B, devido a erros de segurança de tipo. No nosso exemplo é isso que acontece. Primeiro criamos uma variável Raw numberse atribuímos a ela uma coleção genérica ArrayList<Number>. Depois disso adicionamos o número lá 1.
List<String> strings = numbers;
Nesta linha, o compilador tentou nos alertar sobre possíveis erros emitindo o aviso “ Atribuição não verificada... ”, mas nós o ignoramos. Como resultado, temos uma variável genérica de tipo List<String>, que aponta para uma coleção genérica de tipo ArrayList<Number>. Esta situação pode claramente levar a problemas! Isto é o que acontece. Usando nossa nova variável, adicionamos uma string à coleção. A pilha estava poluída - adicionamos primeiro um número e depois uma string à coleção digitada. O compilador nos avisou, mas ignoramos o aviso, recebendo resultados ClassCastExceptionapenas enquanto o programa estava em execução. O que isso tem a ver com isso varargs? O uso varargscom genéricos pode facilmente levar à poluição excessiva. Aqui está um exemplo simples:
import java.util.Arrays;
import java.util.List;

public class Main {

   static void makeHeapPollution(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");

       makeHeapPollution(cars1, cars2);
   }
}
O que está acontecendo aqui? Devido ao apagamento de tipo, nossas planilhas de parâmetros (vamos chamá-las de “folhas” em vez de “listas” por conveniência) são -
List<String>...stringsLists
- se transformará em um array de planilhas - List[]de tipo desconhecido (não esqueça que varargs se transforma em um array regular como resultado da compilação). Por causa disso, podemos facilmente fazer uma atribuição a uma variável Object[] arrayna primeira linha do método - os tipos foram apagados de nossas planilhas! E agora temos uma variável do tipo Object[], onde podemos adicionar qualquer coisa - todos os objetos em Java herdam de Object! No momento, temos apenas uma série de folhas de string. Mas graças ao uso varargse apagamento de tipos, podemos facilmente adicionar-lhes uma folha de números, que é o que fazemos. Como resultado, poluímos a pilha misturando objetos de diferentes tipos. O resultado será a mesma exceção ClassCastExceptionao tentar 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
Estas são as consequências inesperadas que podem resultar da utilização de um mecanismo aparentemente simples varargs:) E é aqui que a nossa palestra de hoje termina. Não se esqueça de resolver alguns problemas e, se ainda tiver tempo e energia, estude literatura adicional. “ Java Efetivo ” não irá ler sozinho! :) Vê você!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION