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!
*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étodosum
. 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 varargs
em 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>
E
main()
addAll()
List
List
Pair<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 Pair
os pares estavam armazenados em nossos objetos <String, String>
. Criar um array não será seguro. Ao usar métodos com varargs
e 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 varargs
avisos 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 varargs
genéricos juntos é a poluição acumulada. A 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 numbers
e 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 ClassCastException
apenas enquanto o programa estava em execução. O que isso tem a ver com isso varargs
? O uso varargs
com 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[] array
na 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 varargs
e 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 ClassCastException
ao 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ê!
GO TO FULL VERSION