Тип Erasure в Java объяснил

Тип Erasure в Java объяснил

1. обзор

В этой быстрой статье мы обсудим основы важного механизма в универсальных шаблонах Java, известного как стирание типов.

2. Что такое стирание типа?

Стирание типа можно объяснить как процесс применения ограничений типа только во время компиляции и отбрасывания информации о типе элемента во время выполнения.

Например:

public static   boolean containsElement(E [] elements, E element){
    for (E e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}

При компиляции несвязанный типE заменяется фактическим типомObject:

public static  boolean containsElement(Object [] elements, Object element){
    for (Object e : elements){
        if(e.equals(element)){
            return true;
        }
    }
    return false;
}

Компилятор обеспечивает безопасность типов нашего кода и предотвращает ошибки во время выполнения.

3. Типы стирания типа

Стирание типа может происходить на уровне класса (или переменной) и метода.

3.1. Стирание типа класса

На уровне класса параметры типа в классе отбрасываются во время компиляции кода и заменяются его первой привязкой илиObject, если параметр типа не привязан.

Давайте реализуемStack, используя массив:

public class Stack {
    private E[] stackContent;

    public Stack(int capacity) {
        this.stackContent = (E[]) new Object[capacity];
    }

    public void push(E data) {
        // ..
    }

    public E pop() {
        // ..
    }
}

При компиляции параметр несвязанного типаE заменяется наObject:

public class Stack {
    private Object[] stackContent;

    public Stack(int capacity) {
        this.stackContent = (Object[]) new Object[capacity];
    }

    public void push(Object data) {
        // ..
    }

    public Object pop() {
        // ..
    }
}

В случае, когда привязан параметр типаE:

public class BoundStack> {
    private E[] stackContent;

    public BoundStack(int capacity) {
        this.stackContent = (E[]) new Object[capacity];
    }

    public void push(E data) {
        // ..
    }

    public E pop() {
        // ..
    }
}

При компиляции параметр связанного типаE заменяется первым связанным классом,Comparable в данном случае:

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. Стирание типа метода

Для стирания типа на уровне метода параметр типа метода не сохраняется, а преобразуется в его родительский типObject, если он не привязан, или это первый связанный класс, когда он привязан.

Давайте рассмотрим способ отображения содержимого любого заданного массива:

public static  void printArray(E[] array) {
    for (E element : array) {
        System.out.printf("%s ", element);
    }
}

При компиляции параметр типаE заменяется наObject:

public static void printArray(Object[] array) {
    for (Object element : array) {
        System.out.printf("%s ", element);
    }
}

Для связанного параметра типа метода:

public static > void printArray(E[] array) {
    for (E element : array) {
        System.out.printf("%s ", element);
    }
}

Мы удалим параметр типаE и заменим его наComparable:

public static void printArray(Comparable[] array) {
    for (Comparable element : array) {
        System.out.printf("%s ", element);
    }
}

4. Краевые Чехлы

Иногда во время процесса стирания типа компилятор создает синтетический метод для дифференциации похожих методов. Они могут исходить от сигнатур методов, расширяющих один и тот же первый связанный класс.

Давайте создадим новый класс, который расширяет нашу предыдущую реализациюStack:

public class IntegerStack extends Stack {

    public IntegerStack(int capacity) {
        super(capacity);
    }

    public void push(Integer value) {
        super.push(value);
    }
}

Теперь посмотрим на следующий код:

IntegerStack integerStack = new IntegerStack(5);
Stack stack = integerStack;
stack.push("Hello");
Integer data = integerStack.pop();

После стирания типа имеем:

IntegerStack integerStack = new IntegerStack(5);
Stack stack = (IntegerStack) integerStack;
stack.push("Hello");
Integer data = (String) integerStack.pop();

Обратите внимание, как мы можем протолкнутьString наIntegerStack - потому чтоIntegerStack унаследовалpush(Object) от родительского классаStack. Это, конечно, неверно - поскольку это должно быть целое число, посколькуintegerStack является типомStack<Integer>.

Поэтому неудивительно, что попыткаpop aString и присвоениеInteger вызываетClassCastException из приведения, вставленного компилятором во времяpush.

4.1. Мостовые методы

Чтобы решить описанный выше крайний случай, компилятор иногда создает метод моста. Это синтетический метод, созданный компилятором Java при компиляции класса или интерфейса, который расширяет параметризованный класс или реализует параметризованный интерфейс, где сигнатуры методов могут немного отличаться или быть неоднозначными.

В нашем примере выше компилятор Java сохраняет полиморфизм универсальных типов после стирания, гарантируя отсутствие несоответствия сигнатуры метода между методомIntegerStack‘spush(Integer) и методомStack‘spush(Object).

Следовательно, компилятор создает метод моста здесь:

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);
    }
}

Следовательно, методpush классаStack после стирания типа делегирует исходный методpush классаIntegerStack.

5. Заключение

В этом руководстве мы обсудили концепцию стирания типа на примерах в переменных и методах параметров типа.

Вы можете прочитать больше об этих понятиях:

Как всегда, доступен исходный код этой статьиover on GitHub.