Философия Java3 - Брюс Эккель
Шрифт:
Интервал:
Закладка:
Неограниченные метасимволы
Казалось бы, неограниченный метасимвол <?<@062> должен означать «все, что угодно», а его использование эквивалентно использованию низкоуровневого типа. В самом деле, на первый взгляд компилятор подтверждает эту оценку:
//: generics/UnboundedWi1dcardsl.java
import java.util.*;
public class UnboundedWildcardsl {
static List listl;
static List<?> list2;
static List<? extends Object> list3;
static void assignldist list) { listl = list; 1i st2 = list;
// list3 = list; // Предупреждение: непроверенное преобразование // Обнаружен List, требуется List<? extends Object>
}
static void assign2(List<?> list) { listl = list; list2 = list; list3 = list;
}
static void assign3(List<? extends Object> list) { listl = list; list2 = list; list3 = list;
}
public static void main(String[] args) { assignl(new ArrayListO): assign2(new ArrayList()); // assign3(new ArrayListO); // Предупреждение-// Непроверенное преобразование. Обнаружен- ArrayList // Требуется: List<? extends Object> assignl(new ArrayLi st<String>0); assign2(new ArrayList<String>0); assign3(new ArrayList<String>0); // Приемлемы обе формы-List<?> wildList = new ArrayListO; wildList = new ArrayList<String>(); assignl(wildList); assign2(wildList); assign3(wildLi st);
}
} ///:-
Во многих ситуациях, подобных рассмотренной, для компилятора совершенно не существенно, используется низкоуровневый тип или <?>. Конструкцию <?> можно считать обычным украшением; впрочем, она обладает некоторой практической ценностью, потому что фактически означает: «Код написан с учетом параметризации Java, и здесь эта конструкция означает не то, что я использую низкоуровневый тип, а то, что параметр параметризации может содержать произвольный тип».
Второй пример демонстрирует важное практическое использование неограниченных метасимволов. Когда вы имеете дело с несколькими параметрами, иногда важно указать, что один параметр может относиться к произвольному типу, а другой ограничить определенным типом:
//: generics/UnboundedWi1dcards2.java
import java.util.*;
public class UnboundedWildcards2 { static Map mapl; static Map<?.?> map2; static Map<String,?> map3; static void assignlCMap map) { mapl = map; } static void assign2(Map<?,?> map) { map2 = map; } static void assign3(Map<String,?> map) { map3 = map; } public static void main(String[] args) { assignl(new HashMapO); assign2(new HashMapO); // assign3(new HashMapO); // Предупреждение: // Непроверенное преобразование. Обнаружен: HashMap // Требуется: Map<String,?> assignl(new HashMap<String,Integer>()); assign2(new HashMap<String,Integer>()); assign3(new HashMap<String.Integer>0):
}
} ///-
Когда в записи используются только неограниченные метасимволы, как в примере Мар<?,?>, компилятор не отличает такой тип от Map. Кроме того, пример UnboundedWildcardsl.java показывает, что компилятор по-разному интерпретирует List<?> и List<? extends Object>.
Ситуация осложняется тем, что компилятор не всегда интересуется различиями между List и List<?> (например), поэтому может показаться, что это одно и то же. В самом деле, поскольку параметризованный аргумент стирается до первого ограничения, List<?> кажется эквивалентным List<Object>, a List, по сути, тоже является List<Object> — однако ни одно из этих утверждений не является в полной мере истинным. List в действительности означает «низкоуровневый List, содержащий любой тип Object», тогда как List<?> означает «не-низкоуровне-вый List, содержащий какой-то конкретный тип, хотя мы не знаем, какой именно».
Когда же компилятор различает низкоуровневые типы и типы с неограниченными метасимволами? В следующем примере используется класс Holder<T>, определение которого приводилось ранее. Класс содержит методы, получающие аргумент Holder, но в разных формах: в виде низкоуровневого типа, с конкретным параметром типа, с неограниченным метасимволом:
//: generics/Wildcards.java // Exploring the meaning of wildcards.
public class Wildcards {
// Низкоуровневый аргумент: static void rawArgs(Holder holder. Object arg) { // holder set(arg); // Предупреждение-// Непроверенный вызов set(T) как члена // низкоуровневого типа Holder // holder, set (new WildcardsO); // To же предупреждение
// Невозможно: нет информации о 'Т' // T t = holder.getO.
// Допустимо, но информация типа теряется Object obj = holder getO.
}
// По аналогии с rawArgsO, но ошибки вместо предупреждений, static void unboundedArg(Holder<?> holder. Object arg) { // holder.set(arg); // Ошибка: // set(capture of ?) in Holder<capture of ?> // не может применяться к (Object) // holder, set (new WildcardsO). // Та же ошибка
// Невозможно; нет информации о 'Т': // T t = holder.get();
// Допустимо, но информация типа теряется: Object obj = holder.getO;
}
static <T> T exactl(Holder<T> holder) { T t = holder.getO; return t;
}
static <T> T exact2(Holder<T> holder. T arg) { holder.set(arg); T t = holder.getO; return t;
}
static <T>
T wildSubtype(Holder<? extends T> holder. T arg) { // holder.set(arg); // Ошибка: // set(capture of ? extends T) in // Holder<capture of ? extends T> // cannot be applied to (T) T t = holder.getO; return t;
}
static <T>
void wildSupertype(Holder<? super T> holder, T arg) { holder.set(arg);
// T t = holder.getO; // Ошибка:
// Несовместимые типы: обнаружен Object, требуется T
// Допустимо, но информация типа теряется: Object obj = holder get О;
}
public static void main(String[] args) {
Holder raw = new Holder<Long>(). // Или
raw = new Holder(),
Holder<Long> qualified = new Holder<Long>(), Holder<?> unbounded = new Holder<Long>(). Holder<? extends Long> bounded = new Holder<Long>(), Long Ing = 1L;
rawArgs(raw. Ing), rawArgs(qualified, Ing), rawArgs(unbounded. 1ng). rawArgs(bounded, Ing);
unboundedArg(raw, Ing), unboundedArg(qualified, Ing), unboundedArg(unbounded, Ing), unboundedArg(bounded, Ing),
// Object rl = exactl(raw); // Предупреждение // Непроверенное преобразование Holder в Holder<T> // Непроверенный вызов метода: exactlCHolder<T>) // применяется к (Holder) Long r2 = exactl(qualified),
Object r3 = exactl(unbounded), // Должен возвращать Object Long r4 = exactl(bounded),
// Long r5 = exact2(raw, Ing); // Предупреждения-
// Непроверенное преобразование Holder в Holder<Long>
// Непроверенный вызов метода. exact2(Holder<T>,T)
// применяется к (Holder,Long)
Long гб = exact2(qualified, Ing),
// Long r7 = exact2(unbounded. Ing), // Ошибка-
// exact2(Holder<T>,T) не может применяться к
// (Holder<capture of ?>,Long)
// Long r8 = exact2(bounded, Ing), // Ошибка.
// exact2(Holder<T>,T) не может применяться
// к (Holder<capture of ? extends Long>,Long)
// Long r9 = wildSubtype(raw, Ing); // Предупреждения
// Непроверенное преобразование Holder
// к Holder<? extends Long>
// Непроверенный вызов метода-
// wildSubtype(Holder<? extends T>,T)
// применяется к (Holder.Long)
Long rlO = wildSubtype(qualified. Ing);
// Допустимо, но возвращать может только Object-
Object rll = wildSubtype(unbounded. Ing).
Long rl2 = wildSubtype(bounded. Ing).
// wildSupertype(raw, Ing); // Предупреждения. // Непроверенное преобразование Holder // к Holder<? super Long> // Непроверенный вызов метода: // wildSupertype(Holder<? super T>,T) // применяется к (Holder.Long) wildSupertype(qualified, Ing), // wildSupertype(unbounded, Ing); // Ошибка:
// wildSupertype(Holder<? super T>,T) не может продолжение&
// применяться к (Holder<capture of ?>,Long) // wiIdSupertypeCbounded, Ing); // Ошибка: // wildSupertype(Holder<? super T>,T) не может // применяться к (Holder<capture of ? extends Long>.Long)
}
} ///:-
В методе rawArgs() компилятор знает, что Holder является параметризованным типом, поэтому несмотря на то, что здесь он выражен как низкоуровневый тип, компилятору известно, что передача Object методу set() небезопасна. Так как в данном случае используется низкоуровневый тип, методу set() можно передать объект произвольного типа, и он будет преобразован в Object. Таким образом, при использовании низкоуровневого типа вы лишаетесь проверки на стадии компиляции. Вызов get() демонстрирует ту же проблему: никакого Т нет, поэтому результатом может быть только Object.
Может создаться впечатление, что низкоуровневый Holder и Holder<?> — приблизительно одно и то же. Однако метод unboundedArgs() демонстрирует различия между ними — в нем выявляются те же проблемы, но информация о них выдается в виде ошибок, а не предупреждений, поскольку низкоуровневый Holder может содержать разнородные комбинации типов, тогда как Holder<?> содержит однородную коллекцию одного конкретного типа.
В exactl() и exact2() используются точные параметры типов (то есть без метасимволов). Мы видим, что exact2() обладает иными ограничениями, нежели exactl(), из-за дополнительного аргумента.
В wildSubtype() ограничения на тип Holder опускаются до Holder с элементами любого типа, удовлетворяющими условию extends Т. И снова это означает, что Т может быть типом Fruit, a holder сможет вполне законно стать Holder <Apple>. Чтобы предотвратить возможное размещение Orange в Holder<Apple>, вызовы set() (и любых других методов, получающих в аргументах параметр типа) запрещены. Однако мы знаем, что все объекты, полученные из Holder<? extends Fruit>, по меньшей мере, являются Fruit, поэтому вызов get() (или любого метода с возвращаемым значением параметра типа) допустим.
Реализация параметризованных интерфейсов
Класс не может реализовать две разновидности одного параметризованного интерфейса — вследствие стирания они будут считаться одним и тем же интерфейсом. Пример конфликта такого рода:
II: generics/MultiplelnterfaceVariants.java
II {CompileTimeError} (He компилируется)
interface Payable<T> {}
class Employee implements Payable<Employee> {}
class Hourly extends Employee
implements Payable<Hourly> {} ///:-
Класс Hourly компилироваться не будет, потому что стирание сокращает Payable<Employee> и Payable<Hourly> до Payable, а в приведенном примере это означало бы двукратную реализацию одного интерфейса. Интересная подробность: если удалить параметризованные аргументы из обоих упоминаний Payable, как это делает компилятор при стирании, программа откомпилируется.
Преобразования типов и предупреждения
Преобразование типа или instanceof с параметром типа не приводит ни к какому эффекту. В следующем контейнере данные хранятся во внутреннем представлении в форме Object и преобразуются к Т при выборке: