Digite Erasure em Java Explained
1. Visão geral
Neste artigo rápido, discutiremos os fundamentos de um mecanismo importante nos genéricos do Java conhecido como apagamento de tipo.
2. O que é apagamento de tipo?
O apagamento de tipo pode ser explicado como o processo de impor restrições de tipo apenas em tempo de compilação e descartar as informações de tipo de elemento em tempo de execução.
Por exemplo:
public static boolean containsElement(E [] elements, E element){
for (E e : elements){
if(e.equals(element)){
return true;
}
}
return false;
}
Quando compilado, o tipo não ligadoE é substituído por um tipo real deObject:
public static boolean containsElement(Object [] elements, Object element){
for (Object e : elements){
if(e.equals(element)){
return true;
}
}
return false;
}
O compilador garante a segurança do tipo do nosso código e evita erros de tempo de execução.
3. Tipos de apagamento de tipo
O apagamento de tipo pode ocorrer nos níveis de classe (ou variável) e método.
3.1. Apagamento de tipo de classe
No nível da classe, os parâmetros de tipo na classe são descartados durante a compilação do código e substituídos por seu primeiro limite, ouObject se o parâmetro de tipo não estiver ligado.
Vamos implementar umStack usando uma matriz:
public class Stack {
private E[] stackContent;
public Stack(int capacity) {
this.stackContent = (E[]) new Object[capacity];
}
public void push(E data) {
// ..
}
public E pop() {
// ..
}
}
Na compilação, o parâmetro de tipo não ligadoE é substituído porObject:
public class Stack {
private Object[] stackContent;
public Stack(int capacity) {
this.stackContent = (Object[]) new Object[capacity];
}
public void push(Object data) {
// ..
}
public Object pop() {
// ..
}
}
Em um caso em que o parâmetro de tipoE está vinculado:
public class BoundStack> {
private E[] stackContent;
public BoundStack(int capacity) {
this.stackContent = (E[]) new Object[capacity];
}
public void push(E data) {
// ..
}
public E pop() {
// ..
}
}
Quando compilado, o parâmetro de tipo de limiteE é substituído pela primeira classe de limite,Comparable neste caso:
public class BoundStack {
private Comparable [] stackContent;
public BoundStack(int capacity) {
this.stackContent = (Comparable[]) new Object[capacity];
}
public void push(Comparable data) {
// ..
}
public Comparable pop() {
// ..
}
}
3.2. Apagamento de tipo de método
Para eliminação de tipo de nível de método, o parâmetro de tipo do método não é armazenado, mas sim convertido em seu tipo paiObject se ele for desvinculado ou se for a primeira classe vinculada quando for vinculado
Vamos considerar um método para exibir o conteúdo de qualquer array:
public static void printArray(E[] array) {
for (E element : array) {
System.out.printf("%s ", element);
}
}
Na compilação, o parâmetro de tipoE é substituído porObject:
public static void printArray(Object[] array) {
for (Object element : array) {
System.out.printf("%s ", element);
}
}
Para um parâmetro do tipo de método vinculado:
public static > void printArray(E[] array) {
for (E element : array) {
System.out.printf("%s ", element);
}
}
Teremos o parâmetro de tipoE apagado e substituído porComparable:
public static void printArray(Comparable[] array) {
for (Comparable element : array) {
System.out.printf("%s ", element);
}
}
4. Casos Edge
Em algum momento do processo de apagamento de tipo, o compilador cria um método sintético para diferenciar métodos semelhantes. Eles podem vir de assinaturas de método que estendem a mesma primeira classe vinculada.
Vamos criar uma nova classe que estende nossa implementação anterior deStack:
public class IntegerStack extends Stack {
public IntegerStack(int capacity) {
super(capacity);
}
public void push(Integer value) {
super.push(value);
}
}
Agora, vamos examinar o seguinte código:
IntegerStack integerStack = new IntegerStack(5);
Stack stack = integerStack;
stack.push("Hello");
Integer data = integerStack.pop();
Após o apagamento do tipo, temos:
IntegerStack integerStack = new IntegerStack(5);
Stack stack = (IntegerStack) integerStack;
stack.push("Hello");
Integer data = (String) integerStack.pop();
Observe como podemos enviar aString emIntegerStack - porqueIntegerStack herdoupush(Object) da classe paiStack. Obviamente, isso está incorreto - pois deveria ser um número inteiro, já queintegerStack é do tipoStack<Integer>.
Portanto, não surpreendentemente, uma tentativa depop aString e atribuir a umInteger causa aClassCastException de um elenco inserido durantepush pelo compilador.
4.1. Métodos de ponte
Para resolver o caso de extremidade acima, o compilador às vezes cria um método de ponte. Este é um método sintético criado pelo compilador Java ao compilar uma classe ou interface que estende uma classe parametrizada ou implementa uma interface parametrizada em que as assinaturas de método podem ser ligeiramente diferentes ou ambíguas.
Em nosso exemplo acima, o compilador Java preserva o polimorfismo de tipos genéricos após o apagamento, garantindo que não haja nenhuma incompatibilidade de assinatura de método entre o métodoIntegerStack'spush(Integer) e o métodoStack'spush(Object).
Portanto, o compilador cria um método de ponte aqui:
public class IntegerStack extends Stack {
// Bridge method generated by the compiler
public void push(Object value) {
push((Integer)value);
}
public void push(Integer value) {
super.push(value);
}
}
Consequentemente, o métodopush da classeStack após o apagamento do tipo, delega ao métodopush original da classeIntegerStack.
5. Conclusão
Neste tutorial, discutimos o conceito de eliminação de tipo com exemplos em variáveis e métodos de parâmetro de tipo.
Você pode ler mais sobre estes conceitos:
Como sempre, o código-fonte que acompanha este artigo está disponívelover on GitHub.