Казалось бы, неограниченный метасимвол <?> должен означать «все, что угодно», а его использование эквивалентно использованию низкоуровневого типа. В самом деле, на первый взгляд компилятор подтверждает эту оценку:
Во многих ситуациях, подобных рассмотренной, для компилятора совершенно не существенно, используется низкоуровневый тип или <?>. Конструкцию <?> можно считать обычным украшением; впрочем, она обладает некоторой практической ценностью, потому что фактически означает: «Код написан с учетом параметризации Java, и здесь эта конструкция означает не то, что я использую низкоуровневый тип, а то, что параметр параметризации может содержать произвольный тип». Второй пример демонстрирует важное практическое использование неограниченных метасимволов. Когда вы имеете дело с несколькими параметрами, иногда важно указать, что один параметр может относиться к произвольному типу, а другой ограничить определенным типом:
Когда в записи используются только неограниченные метасимволы, как в примере Мар<?,?>, компилятор не отличает такой тип от 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 Wildcards());// To же предупреждение
// Невозможно: нет информации о 'Т'
// T t = holder.get();
// Допустимо, но информация типа теряется
Object obj = holder.get();
}
// По аналогии с rawArgs(), но ошибки вместо предупреждений:
// Long r9 = wildSubtype(raw, lng); // Предупреждения
// Непроверенное преобразование Holder
// к Holder<? extends Long>
// Непроверенный вызов метода-
// wildSubtype(Holder<? extends T>,T)
// применяется к (Holder.Long)
Long r10 = wildSubtype(qualified, lng);
// Допустимо, но возвращать может только Object-
Object r11 = wildSubtype(unbounded, lng);
Long r12 = wildSubtype(bounded, lng);
// wildSupertype(raw, lng);// Предупреждения.
// Непроверенное преобразование Holder
// к Holder<? super Long>
// Непроверенный вызов метода:
// wildSupertype(Holder<? super T>,T)
// применяется к (Holder.Long)
wildSupertype(qualified, lng);
// wildSupertype(unbounded, lng); // Ошибка:
// wi1dSupertype(Hoider<? super T>,T) не может
// применяться к (Holder<capture of ?>,Long)
// wildSupertype(bounded, lng); // Ошибка:
// wildSupertype(Holder<? super T>,T) не может
// применяться к (Holder<capture of ? extends Long>.Long)
}
}
В методе rawArgs() компилятор знает, что Holder является параметризованным типом, поэтому несмотря на то, что здесь он выражен как низкоуровневый тип, компилятору известно, что передача Object методу set() небезопасна. Так как в данном случае используется низкоуровневый тип, методу set() можно передать объект произвольного типа, и он будет преобразован в Object. Таким образом, при использовании низкоуровневого типа вы лишаетесь проверки на стадии компиляции. Вызов get() демонстрирует ту же проблему: никакого Тнет, поэтому результатом может быть только Object. Может создаться впечатление, что низкоуровневый Holder и Holder<?> — приблизительно одно и то же. Однако метод unboundedArgs() демонстрирует различия между ними — в нем выявляются те же проблемы, но информация о них выдается в виде ошибок, а не предупреждений, поскольку низкоуровневый Holder может содержать разнородные комбинации типов, тогда как Holder<?> содержит однородную коллекцию одного конкретного типа. В exact1() и exact2() используются точные параметры типов (то есть без метасимволов). Мы видим, что exact2() обладает иными ограничениями, нежели exact1(), из-за дополнительного аргумента. В wildSubtype() ограничения на тип Holder опускаются до Holder с элементами любого типа, удовлетворяющими условию extends Т. И снова это означает, что Т может быть типом Fruit, a holder сможет вполне законно стать Holder <Apple>. Чтобы предотвратить возможное размещение Orange в Holder<Apple>, вызовы set() (и любых других методов, получающих в аргументах параметр типа) запрещены. Однако мы знаем, что все объекты, полученные из Holder<? extends Fruit>, по меньшей мере, являются Fruit, поэтому вызов get() (или любого метода с возвращаемым значением параметра типа) допустим.