Как стирание дженериков «заменяет» несколько границ - PullRequest
0 голосов
/ 25 октября 2019

Я прочитал, что во время процесса стирания типа, компилятор Java стирает все параметры типа и заменяет каждый из них первой границей, если параметр типа ограничен, или Object, если параметр типа не ограничен. Но, я не могу понять, не излишне ли указывать интерфейс, необходимый параметру типа для реализации . Например:

public class Box<T extends Something,Seralizable,Cloneable>

Если стирание заменяет T внутри класса Box на Something (ссылка на класс), это не означает, что интерфейсы: Seralizable,Cloneable должен быть реализован классом Something,так что только я чувствую себя лишним, чтобы указать Seralizable,Cloneable интерфейсы? Кроме того, что произойдет, если внутри алмазов будут упомянуты только интерфейсы, по умолчанию T считается ссылкой Object?

Я буду рад за один пример для универсального класса и один пример для универсального метода (если в обобщенном методе существует несколько расширений).

1 Ответ

0 голосов
/ 25 октября 2019

Вы на самом деле уже ответили на свой вопрос: стирание - это первая граница или объект, если тип не ограничен. Давайте рассмотрим несколько примеров:

public class A implements Serializable {
    //  since the first bound is Cloneable, the erasure is Cloneable
    static <T extends Cloneable & Serializable> void doSomething1(T t) {}

    //  since the first bound is Serializable, the erasure is Serializable
    static <T extends Serializable & Cloneable> void doSomething2(T t) {}

    // since T is not bounded, the erasure is Object
    static <T> void doSomething(T t) {
        System.out.println("A");
    }

    // the type of A is a class, so it has to come first
    // the erasure is A since it comes first
    static <T extends A & Serializable> void doSomething(T t) {
        System.out.println("B");
    }

    // not possible because the bounds are two classes
    // static <T extends Object & A> void doSomething(T t) {return null;}
}

Поскольку стираемые данные разные, методы могут иметь одно и то же имя! Но это совершенно не рекомендуется и довольно запутанно, поскольку поведение меняется:

public static void main(String[] args) {
    A.doSomething(new Object());  // prints "A"
    A.doSomething(new A());       // prints "B"
}

Чтобы ответить на ваш вопрос после редактирования: Нет, он не является лишним. Указание типа, который должен быть реализован параметром типа, дает вам доступ к методам границ. Давайте рассмотрим следующий пример:

public final class Box<T extends A & Comparable<T>> {
    private T item1;
    private T item2;

    int compare() {
        // this is the compare method of Comparable interface
        return item1.compareTo(item2);
    }
}

Из приведенного выше примера вы видите, что A не реализует интерфейс Comparable. Это означает, что если вы просто напишите Box<T extends A>, вы не сможете сравнить два элемента в вашем Box, используя метод compareTo просто потому, что A не реализует Comparable. Если вы хотите, чтобы элементы вашего ящика имели класс A и для реализации интерфейса Comparable, вам нужно указать обе границы.

Это не ссылка A, которая реализуетComparable но ссылка T! Даже если стирание T extends A & Comparable<T> будет A, компилятор может выполнять приведение на более низком уровне. И это то, что здесь происходит. Это можно увидеть, если проверить байт-код с помощью утилиты javap, где используется инструкция checkcast :

  int compare();
         ....
         0: aload_0
         1: getfield      #7                  // Field item1:LA;
         4: checkcast     #13                 // class java/lang/Comparable
         7: aload_0
         8: getfield      #15                 // Field item2:LA;
        11: invokeinterface #18,  2           // InterfaceMethod java/lang/Comparable.compareTo:(Ljava/lang/Object;)I
        16: ireturn
        ....
...