Spec-Zone .ru
спецификации, руководства, описания, API
След: Премия
Урок: Обобщения
Универсальные Методы
Домашняя страница > Премия > Обобщения

Универсальные Методы

Рассмотрите запись метода, который берет массив объектов и набора и помещает все объекты в массив в набор. Вот первая попытка:

static void fromArrayToCollection(Object[] a, Collection<?> c) {
    for (Object o : a) { 
        c.add(o); // compile-time error
    }
}

К настоящему времени Вы будете учиться избегать ошибки новичка попытки использовать Collection<Object> как тип параметра набора. Вы можете или, возможно, не распознали то использование Collection<?> не собирается работать также. Вспомните, что невозможно просто пихнуть объекты в набор неизвестного типа.

Способ заключить сделку с этими проблемами состоит в том, чтобы использовать универсальные методы. Точно так же как описания типа объявления метода могут быть универсальными — то есть, параметризованный одним или более параметрами типа.

static <T> void fromArrayToCollection(T[] a, Collection<T> c) {
    for (T o : a) {
        c.add(o); // Correct
    }
}

Мы можем вызвать этот метод с любым видом набора, тип элемента которого является супертипом типа элемента массива.

Object[] oa = new Object[100];
Collection<Object> co = new ArrayList<Object>();

// T inferred to be Object
fromArrayToCollection(oa, co); 

String[] sa = new String[100];
Collection<String> cs = new ArrayList<String>();

// T inferred to be String
fromArrayToCollection(sa, cs);

// T inferred to be Object
fromArrayToCollection(sa, co);

Integer[] ia = new Integer[100];
Float[] fa = new Float[100];
Number[] na = new Number[100];
Collection<Number> cn = new ArrayList<Number>();

// T inferred to be Number
fromArrayToCollection(ia, cn);

// T inferred to be Number
fromArrayToCollection(fa, cn);

// T inferred to be Number
fromArrayToCollection(na, cn);

// T inferred to be Object
fromArrayToCollection(na, co);

// compile-time error
fromArrayToCollection(na, cs);

Заметьте, что мы не должны передать фактический параметр типа универсальному методу. Компилятор выводит параметр типа за нас, основанный на типах фактических параметров. Это обычно выведет параметр наиболее определенного типа, который выполнит корректный типом вызов.

Один вопрос, который возникает: когда я должен использовать универсальные методы, и когда я должен использовать подстановочные типы? Чтобы понять ответ, давайте исследуем несколько методов от Collection библиотеки.

interface Collection<E> {
    public boolean containsAll(Collection<?> c);
    public boolean addAll(Collection<? extends E> c);
}

Мы, возможно, использовали универсальные методы здесь вместо этого:

interface Collection<E> {
    public <T> boolean containsAll(Collection<T> c);
    public <T extends E> boolean addAll(Collection<T> c);
    // Hey, type variables can have bounds too!
}

Однако, в обоих containsAll и addAll, параметр типа T используется только однажды. Тип возврата не зависит от параметра типа, ни делает любой другой параметр методу (в этом случае, просто есть только один параметр). Это говорит нам, что параметр типа используется для полиморфизма; его единственный эффект состоит в том, чтобы позволить множеству фактических типов параметра использоваться на различных сайтах вызова. Если это так, нужно использовать подстановочные знаки. Подстановочные знаки разрабатываются, чтобы поддерживать гибкое выделение подтипов, которое является тем, что мы пытаемся выразить здесь.

Универсальные методы позволяют параметрам типа использоваться, чтобы выразить зависимости среди типов одного или более параметров методу и/или его типа возврата. Если нет такой зависимости, универсальный метод не должен использоваться.

Возможно использовать и универсальные методы и подстановочные знаки в тандеме. Вот метод Collections.copy():

class Collections {
    public static <T> void copy(List<T> dest, List<? extends T> src) {
    ...
}

Отметьте зависимость между типами этих двух параметров. Любой объект, скопированный с исходного списка, src, должно быть присваиваемым типу элемента T из целевого списка, dst. Так тип элемента src может быть любой подтип T— мы не заботимся который. Подпись copy выражает зависимость, используя параметр типа, но использует подстановочный знак для типа элемента второго параметра.

Мы, возможно, записали подпись для этого метода иначе, не используя подстановочные знаки вообще:

class Collections {
    public static <T, S extends T> void copy(List<T> dest, List<S> src) {
    ...
}

Это прекрасно, но в то время как первый параметр типа используется оба в типе dst и в связанном из второго параметра типа, S, S непосредственно только используется однажды, в типе src— ничто иное не зависит от этого. Это - знак, что мы можем заменить S с подстановочным знаком. Используя подстановочные знаки является более четким и более кратким чем объявление явных параметров типа, и должен поэтому быть предпочтен когда бы ни было возможно.

У подстановочных знаков также есть преимущество, что они могут использоваться за пределами сигнатур методов, как типы полей, локальных переменных и массивов. Вот пример.

Возвращаясь к нашей проблеме рисования формы, предположите, что мы хотим сохранить историю рисования запросов. Мы можем поддержать историю в статической переменной в class Shape, и имейте drawAll() сохраните его входящий параметр в поле истории.

static List<List<? extends Shape>> 
    history = new ArrayList<List<? extends Shape>>();

public void drawAll(List<? extends Shape> shapes) {
    history.addLast(shapes);
    for (Shape s: shapes) {
        s.draw(this);
    }
}

Наконец, снова давайте принимать во внимание соглашение о присвоении имен, используемое для параметров типа. Мы используем T для типа, всякий раз, когда нет ничего более определенного о типе, чтобы отличить это. Это часто имеет место в универсальных методах. Если есть многократные параметры типа, мы могли бы использовать буквы тот сосед T в алфавите, такой как S. Если универсальный метод появляется в универсальном class, это - хорошая идея избегать использования тех же самых имен для параметров типа метода и class, избежать беспорядка. То же самое применяется к вложенным универсальным классам.


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

Предыдущая страница: Подстановочные знаки
Следующая страница: Взаимодействие с Кодом Наследства