Шаг 66.
Язык программирования Java.
Преобразование обобщенных выражений и методов

На этом шаге мы поговорим про преобразование обобщенных выражений и методов

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

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

В результате стирания из метода getFirst() возвращается тип Object. Поэтому компилятор автоматически вводит приведение к типу Employee. Это означает, что компилятор преобразует вызов данного метода в следующие две команды для виртуальной машины:

Отметим, что операции приведения типов вводятся также при обращении к обобщенному полю.

Стирание типов происходит и в обобщенных методах. Программисты обычно воспринимают обобщенные методы как целое семейство методов вроде следующего:

public static <T extends Comparable> T min(T[] a) {...}

Но после стирания типов остается только один метод. Обратим внимание на то, что параметр обобщенного типа T стирается, а остается только ограничивающий тип Comparable.

public static Comparable min(Comparable[] a) {...}

Стирание типов в обобщенных методах приводит некоторым осложнениям. Рассмотрим следующий пример:

class DateInterval extends Pair<Date> {
    public void setSecond(Date second) {
        if (second.compareTo(getFirst()) >= 0) {
            super.setSecond(second);
        }
    }
    ...
}

В этом фрагменте кода интервал дат задается парой объектов типа Date, и поэтому соответствующие методы требуется переопределить, чтобы второе сравниваемое значение не было меньше первого. В результате стирания данный класс преобразуется в следующий:

class DateInterval extends Pair {
    public void setSecond(Date second) {...}
    ...
}

Помимо этого метода имеется и другой метод setSecond(), унаследованный от класса Pair, а именно:

public void setSecond(Object second) {...}

Рассмотрим следующую последовательность операторов:

DateInteval interval = new DateInterval(...);
Pair<Date> pair = interval;
pair.setSecond(aDate);

Предполагается, что вызов метода setSecond() является полиморфным, и поэтому вызывается соответствующий метод. А поскольку переменная pair ссылается на объект типа DateInterval, это должен быть вызов DateInterval.setSecond(). Но дело в том, что стирание типов мешает соблюдению принципа полиморфизма. В качестве выхода из этого затруднительного положения компилятор формирует следующий мостовой метод в классе DateInterval:

public void setSecond(Object second) {
	setSecond((Date) second);
}

Обратим внимание на следующую строку:

pair.setSecond(aDate);

В объявлении переменной pair указан тип Pair<Date>, и к этому типу относится только один метод под именем setSecond(), а именно setSecond(Object). Виртуальная машина вызывает этот метод для того объекта, на который ссылается переменная pair. Этот объект относится к типу DateInterval, и поэтому вызывается метод DateInterval.setSecond(Object). Именно он и является синтезированным мостовым методом. Именно он вызывает метод DateInterval.setSecond(Date).

Мостовые методы могут быть еще более необычными. Допустим, метод из класса DateInterval также переопределяется метод getSecond(), как показано ниже:

class DateInterval extends Pair<Date> {
    ...
    public Date getSecond() {
        return (Date) super.getSecond();
    }
    ...
}

В классе DateInterval имеются следующие два метода под именем getSecond():

Date getSecond(); /*определен в классе DateInterval*/
Object getSecond(); /*переопределяет метод из класса Pair для вызова первого метода*/

Таким образом, о преобразовании обобщений в Java нужно запомнить следующее:

На следующем шаге мы рассмотрим ограничения и пределы обобщений

Предыдущий шаг Содержание Следующий шаг