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 это может сравниться Strings. Это может быть сделано 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, независимо от его типа элемента.

Поскольку эта ситуация возникает относительно часто, есть специальное правило, которое позволяет такой код при очень определенных обстоятельствах, при которых код, как могут доказывать, безопасен. Это правило, известное как подстановочное получение, позволяет компилятору выводить неизвестный тип подстановочного знака как параметр типа универсальному методу.


Проблемы с примерами? Попытайтесь Компилировать и Выполнить Примеры: FAQ.
Жалобы? Поздравление? Предложения? Дайте нам свою обратную связь.

Предыдущая страница: Литералы Класса как Маркеры Типа времени выполнения
Следующая страница: Преобразование Кода Наследства, чтобы Использовать Обобщения