Эккель Брюс
Шрифт:
Может создаться впечатление, что низкоуровневый 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 и преобразуются к Т при выборке:
//. generics/GenericCast.java
class FixedSizeStack<T> { private int index = 0; private Object[] storage; public FixedSizeStackOnt size) {
storage = new Object[size];
}
public void push(T item) { storage[index++] = item; }
@SuppressWarni ngs("unchecked")
public T popО { return (T)storage[--index], }
}
public class GenericCast {
public static final int SIZE = 10; public static void main(String[] args) { FixedSizeStack<String> strings =
new FixedSizeStack<String>(SIZE); for (String s • "А В С D E F G H I J".splitC' "))
strings.push(s), for(int i = 0. i < SIZE; i++) {
String s = strings pop; System.out.print(s + " ");
} /* Output:
JIHGFEDCBA
*///:-
Без директивы @SuppressWarnings компилятор выдает для рор предупреждение о «непроверенном преобразовании». Вследствие стирания он не знает, безопасно преобразование или нет, поэтому метод рор никакого преобразования не выполняет. Т стирается до первого ограничения, которым по умолчанию является Object, так что рор на самом деле преобразует Object в Object.
Перегрузка
Следующий пример не компилируется, хотя на первый взгляд выглядит вполне разумно:
// generics/UseList java
// {CompileTimeError} (He компилируется)
import java.util.*;
public class UseList<W.T> { void f(List<T> v) {}
void f(List<W> v) {}
} III ~
Перегрузка метода создает идентичную сигнатуру типа вследствие стирания. В таких случаях следует определять методы с различающимися именами:
II. generics/UseList2 java
import java util.*;
public class UseList2<W.T> { void fl(List<T> v) {} void f2(List<W> v) {}
} III.-
К счастью, проблемы такого рода обнаруживаются компилятором.
Резюме
Мне довелось работать с шаблонами С++ с момента их появления. Скорее всего, приведенный далее аргумент я выдвигал в спорах чаще, чем большинство моих единомышленников. Лишь недавно я задумался над тем, насколько в действительности справедлив этот аргумент, — сколько раз проблема, которую я сейчас опишу, проникала в рабочий код?