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 -
A poluição heap pode acontecer na seguinte situação:
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 umsum
mé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 E
objetos 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 String
matriz 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 Pair
objetos 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 @SafeVarargs
anotaçõ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. 
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 A
deveriam estar no heap, mas os objetos do tipo B
acabam lá devido a erros relacionados à segurança do tipo. No nosso exemplo, é exatamente isso que acontece. Primeiro, criamos a numbers
variá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 ClassCastException
apenas 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[] array
variá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 ClassCastException
quando 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!
GO TO FULL VERSION