С другой стороны, вызов метода, возвращающего Fruit, безопасен; мы знаем, что все элементы List должны по меньшей мере относиться к Fruit, поэтому компилятор это позволит.
Насколько умен компилятор?
Казалось бы, из всего сказанного следует, что вызов любых методов с аргументами невозможен, но рассмотрим следующий пример:
//: generics/Compi1erIntel 1 igence.java
import java.util .*;
public class Compilerlntelligence {
public static void main(String[] args) { List extends Fruit> flist =
Arrays.asList(new AppleO); Apple a = (Apple)flist.get(O); // Без предупреждений fli st. contains (new AppleO); //Аргумент 'Object' fl i st. indexOf (new AppleO); //Аргумент 'Object'
}
} ///:-
Как видите, вызовы contains() и indexOf() с аргументами Apple воспринимаются нормально. Означает ли это, что компилятор действительно анализирует код, чтобы узнать, модифицирует ли некоторый метод свой объект?
Просмотр документации ArrayList показывает, что компилятор не настолько умен. Если add() получает аргумент параметризующего типа, contains() и in-dexOf() получают аргумент типа Object. Таким образом, когда вы указываете ArrayList extends Fruit>, аргумент add() превращается в «? extends Fruit». По этому описанию компилятор не может определить, какой именно подтип Fruit требуется в данном случае, поэтому не принимает никакие типы Fruit. Даже если вы предварительно преобразуете Apple в Fruit, компилятор все равно откажется вызывать метод (например, add()), если в списке аргументов присутствует метасимвол.
У методов contains() и indexOf() аргументы относятся к типу Object, метасимволы в них отсутствуют, поэтому компилятор разрешает вызов. Это означает, что проектировщик параметризованного класса должен сам решить, какие вызовы «безопасны», и использовать типы Object для их аргументов. Чтобы сделать невозможным вызов при использовании типа с метасимволами, включите параметр типа в список аргументов.
В качестве примера рассмотрим очень простой класс Holder:
//: generics/Holder.java
public class Holder
public Holder(T val) { value = val; } public void set(T val) { value = val; } public T getО { return value; } public boolean equals(Object obj) { return value.equals(obj);
}
public static void main(String[] args) {
Holder
// Holder
d = (Apple)fruit.getO; // Возвращает 'Object' try {
Orange с = (Orange)fruit.getO; // Предупреждения нет } catch(Exception e) { System.out.println(e); } // fruit.set(new AppleO); // Вызов setO невозможен // fruit.set(new FruitO); // Вызов setO невозможен System.out.println(fruit.equals(d)); // OK
}
} /* Output: (Sample)
java.lang.ClassCastException. Apple cannot be cast to Orange
true
*///:-
Holder содержит метод set(), получающий T; метод get(), возвращающий Т; и метод equals(), получающий Object. Как вы уже видели, Holder
Впрочем, метод equalsQ работает нормально, потому что он получает Object вместо Т. Таким образом, компилятор обращает внимание только на типы передаваемых и возвращаемых объектов. Он не анализирует код, проверяя, выполняются ли реальные операции чтения или записи.
Контравариантность
Также можно пойти другим путем и использовать
//• generics/SuperTypeWiIdcards java
import java util.*: