На этом шаге мы поговорим про преобразование обобщенных выражений и методов
Когда в программе вызывается обобщенный метод, компилятор вводит операции приведения типов при стирании возвращаемого типа. Рассмотрим следующий пример:
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 нужно запомнить следующее:
На следующем шаге мы рассмотрим ограничения и пределы обобщений