Стирание типов (Type Erasure) — это механизм в Java, который обеспечивает совместимость дженериков (обобщенных типов) с кодом, написанным до появления дженериков (Java 5). Этот механизм удаляет информацию о типах во время компиляции, что накладывает определенные ограничения на использование дженериков. Давайте разберем, как работает стирание типов и какие ограничения оно накладывает.
Стирание типов — это процесс, при котором компилятор Java удаляет информацию о типах-параметрах (дженериках) и заменяет их на их ограничения (обычно Object
или указанный верхний предел). Это делается для обеспечения совместимости с кодом, который не использует дженерики.
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
После компиляции этот класс будет выглядеть так:
public class Box {
private Object value;
public void setValue(Object value) {
this.value = value;
}
public Object getValue() {
return value;
}
}
Тип T
заменен на Object
, так как T
не имеет ограничений.
Типы-параметры заменяются на их верхний предел. Если верхний предел не указан, используется Object
.
Пример:
public class Box<T extends Number> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
После компиляции:
public class Box {
private Number value;
public void setValue(Number value) {
this.value = value;
}
public Number getValue() {
return value;
}
}
Тип T
заменен на Number
, так как T
ограничен сверху типом Number
.
Для поддержки полиморфизма компилятор генерирует мостовые методы, которые обеспечивают корректное поведение при наследовании и переопределении методов.
Пример:
class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
class IntegerBox extends Box<Integer> {
@Override
public void setValue(Integer value) {
super.setValue(value);
}
@Override
public Integer getValue() {
return super.getValue();
}
}
Компилятор сгенерирует мостовой метод для setValue
:
public void setValue(Object value) {
setValue((Integer) value);
}
Из-за стирания типов невозможно создать экземпляр типа-параметра, так как во время выполнения информация о типе отсутствует.
Пример:
public class Box<T> {
public T createInstance() {
return new T(); // Ошибка компиляции
}
}
Дженерики не поддерживают примитивные типы, так как они заменяются на Object
.
Пример:
Box<int> box = new Box<>(); // Ошибка компиляции
Box<Integer> box = new Box<>(); // Корректно
Из-за стирания типов методы с одинаковыми типами-параметрами будут иметь одинаковую сигнатуру после компиляции.
Пример:
public class Box {
public void setValue(List<String> list) {}
public void setValue(List<Integer> list) {} // Ошибка компиляции
}
Типы-параметры не могут быть использованы в статических полях, методах или блоках, так как они относятся к экземплярам класса.
Пример:
public class Box<T> {
private static T value; // Ошибка компиляции
}
Из-за стирания типов невозможно проверить тип объекта, параметризованного дженериком, во время выполнения.
Пример:
Box<Integer> box = new Box<>();
if (box instanceof Box<Integer>) { // Ошибка компиляции
// ...
}
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Main {
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello");
System.out.println(stringBox.getValue()); // Вывод: Hello
}
}
public class NumberBox<T extends Number> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
public class Main {
public static void main(String[] args) {
NumberBox<Integer> intBox = new NumberBox<>();
intBox.setValue(42);
System.out.println(intBox.getValue()); // Вывод: 42
}
}
Понимание стирания типов помогает избежать ошибок при работе с дженериками и правильно проектировать обобщенные классы и методы.