Atualizado: 02/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

Type Erasure em Java (Remoção de Tipos Genéricos)

Embora o Java permita criar classes e métodos genéricos com parâmetros de tipo, esses tipos não existem de fato em tempo de execução. Na Máquina Virtual Java (JVM), todos os objetos pertencem a classes concretas, e os tipos genéricos são apenas uma abstração de compilação.

Durante o processo de compilação, os parâmetros de tipo são removidos e substituídos por tipos concretos — geralmente Object, ou por tipos definidos como limites (restrições) dos parâmetros. Esse processo é chamado de type erasure, ou remoção de tipos.


Exemplo básico de remoção de tipo

Considere a seguinte classe genérica:

class Person<T> {

    private T id;
    private String name;

    T getId() { return id; }
    String getName() { return name; }

    Person(T id, String name) {
        this.id = id;
        this.name = name;
    }
}

Como o parâmetro de tipo T não tem restrições, o compilador o substitui por Object. Após o processo de type erasure, a JVM enxerga algo equivalente a isto:

class Person {

    private Object id;
    private String name;

    Object getId() { return id; }
    String getName() { return name; }

    Person(Object id, String name) {
        this.id = id;
        this.name = name;
    }
}

Assim, pouco importa o tipo genérico usado na criação do objeto:

Person tom = new Person<Integer>(456, "Tom");
Person bob = new Person<String>("qwert", "Bob");

Em ambos os casos, em tempo de execução, o tipo T é tratado como Object.


Conversões automáticas após a remoção de tipo

Quando o resultado de um método genérico ou um campo é atribuído a uma variável de tipo específico, o compilador insere automaticamente as conversões necessárias. Por exemplo:

var tom = new Person<Integer>(456, "Tom");
int tomId = tom.getId();
System.out.println(tomId);

Após a remoção dos tipos genéricos, o código é tratado internamente como:

var tom = new Person(456, "Tom");
int tomId = (int) tom.getId();
System.out.println(tomId);

O compilador gera o cast para garantir a compatibilidade entre o tipo genérico e o tipo real da variável.


Parâmetros de tipo com restrições

Agora, veja um exemplo com limite de tipo (bounded type parameter):

class Message {

    private String text;
    String getText() { return text; }

    Message(String text) {
        this.text = text;
    }
}

class Messenger<T extends Message> {

    private T message;
    T getMessage() { return this.message; }

    Messenger(T message) { this.message = message; }

    void send() {
        System.out.println("Enviando mensagem: " + message.getText());
    }
}

O parâmetro de tipo T tem uma restrição: ele deve ser uma subclasse de Message. Portanto, após o type erasure, o tipo T é substituído por Message:

class Messenger {

    private Message message;
    Message getMessage() { return this.message; }

    Messenger(Message message) { this.message = message; }

    void send() {
        System.out.println("Enviando mensagem: " + message.getText());
    }
}

Múltiplas restrições de tipo

Um parâmetro genérico pode ter vários limites, como uma classe base e um ou mais interfaces. Nesse caso, o compilador substitui o tipo genérico pelo primeiro limite declarado. Se for necessário acessar membros definidos nos outros limites, o compilador insere casts apropriados.

Exemplo:

class Messenger<T extends Message & Printable> {

    void sendMessage(T message) {
        message.print();
    }
}

interface Printable {
    void print();
}

class Message {

    private String text;
    String getText() { return text; }

    Message(String text) {
        this.text = text;
    }
}

Aqui, T deve representar um tipo que herda de Message e implementa Printable. Após o type erasure, o compilador gera algo equivalente a:

class Messenger {

    void sendMessage(Message message) {
        ((Printable) message).print();
    }
}

O parâmetro T é substituído por Message (o primeiro limite), e para acessar os métodos de Printable, o compilador insere uma conversão explícita.


Resumo

  • O type erasure remove os tipos genéricos durante a compilação, substituindo-os por Object ou pelo primeiro limite definido.
  • A JVM não mantém informações de tipo genérico em tempo de execução.
  • O compilador adiciona casts automaticamente para preservar a segurança de tipos.
  • Em parâmetros genéricos com múltiplas restrições, apenas o primeiro limite é usado como tipo base, e os demais são acessados por conversões.
  • Esse comportamento garante compatibilidade retroativa com código Java anterior à introdução de genéricos (Java 5).