Spec-Zone .ru
спецификации, руководства, описания, API
|
В этом разделе мы рассмотрим часть более усовершенствованного использования подстановочных знаков. Мы видели несколько примеров, где ограниченные подстановочные знаки были полезны, читая из структуры данных. Теперь рассмотрите инверсию, структуру данных только для записи. Интерфейс Sink
простой пример этого вида.
interface Sink<T> { flush(T t); }
Мы можем предположить использовать это как демонстрирующийся кодом ниже. Метод writeAll()
разрабатывается, чтобы сбросить все элементы набора coll
к приемнику snk
, и возвратите последний сброшенный элемент.
public static <T> T writeAll(Collection<T> coll, Sink<T> snk) { T last; for (T t : coll) { last = t; snk.flush(last); } return last; } ... Sink<Object> s; Collection<String> cs; String str = writeAll(cs, s); // Illegal call.
Как записано, звонок writeAll()
недопустимо, поскольку никакой допустимый параметр типа не может быть выведен; ни один String
ни Object
соответствующие типы для T
, потому что Collection
элемент и Sink
элемент должен иметь тот же самый тип.
Мы можем фиксировать эту ошибку, изменяя подпись writeAll()
как показано ниже, используя подстановочный знак.
public static <T> T writeAll(Collection<? extends T>, Sink<T>) {...} ... // Call is OK, but wrong return type. String str = writeAll(cs, s);
Вызов является теперь законным, но присвоение ошибочно, так как выведенный тип возврата Object
потому что T
соответствует тип элемента s
, который является Object
.
Решение состоит в том, чтобы использовать форму ограниченного подстановочного знака, который мы еще не видели: подстановочные знаки с нижней границей. Синтаксис ? super T
обозначает неизвестный тип, который является супертипом T
(или T
непосредственно; помните, что отношение супертипа рефлексивно). Это - двойные из ограниченных подстановочных знаков, которые мы использовали, где мы используем ? extends T
обозначить неизвестный тип, который является подтипом T
.
public static <T> T writeAll(Collection<T> coll, Sink<? super T> snk) { ... } String str = writeAll(cs, s); // Yes!
Используя этот синтаксис, вызов является законным, и выведенный тип String
, как требующийся.
Теперь давайте поворачиваться к более реалистическому примеру. A java.util.TreeSet<E>
представляет дерево элементов типа E
это упорядочивается. Один способ создать a TreeSet
должен передать a Comparator
возразите против конструктора. Тот компаратор будет использоваться, чтобы сортировать элементы TreeSet
согласно требуемому упорядочиванию.
TreeSet(Comparator<E> c)
Comparator
интерфейс по существу:
interface Comparator<T> { int compare(T fst, T snd); }
Предположите, что мы хотим создать a TreeSet<String>
и передача в подходящем компараторе, Мы должны передать это a Comparator
это может сравниться String
s. Это может быть сделано a Comparator<String>
, но a Comparator<Object>
сделает точно также. Однако, мы не будем в состоянии вызвать конструктора, данного выше на a Comparator<Object>
. Мы можем использовать более низкий ограниченный подстановочный знак, чтобы получить гибкость, которую мы хотим:
TreeSet(Comparator<? super E> c)
Этот код позволяет любому применимому компаратору использоваться.
Как заключительный пример использования более низких ограниченных подстановочных знаков, позволяет взгляду на метод Collections.max()
, который возвращает максимальный элемент в наборе, который передают к этому как параметр. Теперь, для max()
чтобы работать, все элементы набора, передаваемого в, должны реализовать Comparable
. Кроме того они должны все быть сопоставимыми друг с другом.
Первая попытка generifying эта сигнатура метода урожаи:
public static <T extends Comparable<T>> T max(Collection<T> coll)
Таким образом, метод берет набор некоторого типа T
это сопоставимо с собой, и возвращает элемент того типа. Однако, этот код, оказывается, является слишком рестриктивным. Чтобы видеть почему, рассмотрите тип, который сопоставим с произвольными объектами:
class Foo implements Comparable<Object> { ... } Collection<Foo> cf = ... ; Collections.max(cf); // Should work.
Каждый элемент cf
сопоставимо с любым элементом в cf
, так как каждый такой элемент является a Foo
, который сопоставим с любым объектом, и в особенности другому Foo
. Однако, используя подпись выше, мы находим, что требование отклоняется. Выведенный тип должен быть Foo
, но Foo
не реализует Comparable<Foo>
.
Это не необходимо это T
будьте сопоставимы с точно непосредственно. Все это требуется, является этим T
будьте сопоставимы с одним из его супертипов. Это дает нам:
public static <T extends Comparable<? super T>> T max(Collection<T> coll)
Отметьте что фактическая подпись Collections.max()
более включается. Мы возвращаемся к этому в следующем разделе, Преобразовывая Код Наследства, чтобы Использовать Обобщения. Это рассуждение применяется к почти любому использованию Comparable
это предназначается, чтобы работать на произвольные типы: Вы всегда хотите использовать Comparable<? super T>
.
Вообще, если у Вас есть API, который только использует параметр типа T
как параметр, его использование должно использовать в своих интересах ниже ограниченные подстановочные знаки (? super T
). Наоборот, если API только возвращается T
, Вы дадите Вашим клиентам больше гибкости при использовании верхних ограниченных подстановочных знаков (? extends T
).
Это должно быть довольно четким к настоящему времени что данный:
Set<?> unknownSet = new HashSet<String>(); ... /* Add an element t to a Set s. */ public static <T> void addToSet(Set<T> s, T t) { ... }
Вызов ниже недопустим.
addToSet(unknownSet, "abc"); // Illegal.
Это не имеет никакого значения, что фактический набор, который передают, является рядом строк; что вопросы то, что выражение, которое передают как параметр, является рядом неизвестного типа, который, как могут гарантировать, не будет рядом строк, или любого типа в частности.
Теперь, рассмотрите следующий код:
class Collections { ... <T> public static Set<T> unmodifiableSet(Set<T> set) { ... } } ... Set<?> s = Collections.unmodifiableSet(unknownSet); // This works! Why?
Кажется, что это не должно быть позволено; все же, смотря на этот определенный вызов, совершенно безопасно разрешить это. В конце концов, unmodifiableSet()
действительно работает на любого отчасти Set
, независимо от его типа элемента.
Поскольку эта ситуация возникает относительно часто, есть специальное правило, которое позволяет такой код при очень определенных обстоятельствах, при которых код, как могут доказывать, безопасен. Это правило, известное как подстановочное получение, позволяет компилятору выводить неизвестный тип подстановочного знака как параметр типа универсальному методу.