След: Премия
Урок: Обобщения
Подстановочные знаки
Домашняя страница > Премия > Обобщения

Подстановочные знаки

Рассмотрите проблему записи подпрограммы, которая распечатывает все элементы в наборе. Вот то, как Вы могли бы записать это в более старой версии языка (то есть, пред5.0 выпусков):

void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}

И вот наивная попытка записи этого использующий обобщения (и новое for синтаксис цикла):

void printCollection(Collection<Object> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

Проблема состоит в том, что эта новая версия намного менее полезна чем старая. Принимая во внимание, что старый код можно было вызвать с любым видом набора в качестве параметра, новый код только берет Collection<Object>, который, поскольку мы только что демонстрировали, не является супертипом всех видов наборов!

Так, каков супертип всех видов наборов? Это пишется Collection<?> (объявленный "набор неизвестных"), то есть, набор, тип элемента которого соответствует что-либо. Это вызвало подстановочный тип по очевидным причинам. Мы можем записать:

void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}

и теперь, мы можем вызвать это с любым типом набора. Заметьте ту внутреннюю часть printCollection(), мы можем все еще считать элементы из c и дайте им тип Object. Это всегда безопасно, с тех пор безотносительно фактического типа набора, это содержит объекты. Не безопасно добавить произвольные объекты к этому однако:

Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error

Так как мы не знаем что тип элемента c стенды для, мы не можем добавить объекты к этому. add() метод берет параметры типа E, тип элемента набора. Когда фактический параметр типа ?, это обозначает некоторый неизвестный тип. Любой параметр мы передаем к add должен был бы быть подтип этого неизвестного типа. Так как мы не знаем, в каком типе то есть, мы ничего не можем передать. Единственное исключение null, который является элементом каждого типа.

С другой стороны, данный a List<?>, мы можем вызвать get() и используйте результат. Тип результата является неизвестным типом, но мы всегда знаем, что это - объект. Поэтому безопасно присвоить результат get() к переменной типа Object или передайте это в качестве параметра где тип Object ожидается.

Ограниченные Подстановочные знаки

Рассмотрите простое заявление рисунка, которое может потянуть формы, такие как прямоугольники и круги. Чтобы представить эти формы в пределах программы, Вы могли определить иерархию class, такую как это:

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

Эти классы могут быть оттянуты на холсте:

public class Canvas {
    public void draw(Shape s) {
        s.draw(this);
   }
}

Любой рисунок будет обычно содержать много форм. Предположение, что они представляются как список, было бы удобно иметь метод в Canvas это тянет их всех:

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

Теперь, правила типа говорят это drawAll() может только быть вызван в списках точно Shape: к этому нельзя, например, обратиться a List<Circle>. Это неудачно, так как весь метод делает читается формы из списка, таким образом, к этому можно было точно также обратиться a List<Circle>. То, что мы действительно хотим, для метода, чтобы принять список любого вида формы:

public void drawAll(List<? extends Shape> shapes) {
    ...
}

Здесь есть небольшое, но очень важное различие: мы заменили тип List<Shape> с List<? extends Shape>. Теперь drawAll() примет списки любого подкласса Shape, таким образом, мы можем теперь вызвать это на a List<Circle> если мы хотим.

List<? extends Shape> пример ограниченного подстановочного знака. ? стенды для неизвестного типа, точно так же как подстановочные знаки мы видели ранее. Однако, в этом случае, мы знаем, что этот неизвестный тип является фактически подтипом Shape. (Отметьте: Это могло быть Shape непосредственно, или некоторый подкласс; это не должно буквально расшириться Shape.) Мы говорим это Shape верхняя граница подстановочного знака.

Есть, как обычно, цена, которая будет заплачена за гибкость использования подстановочных знаков. Та цена - то, что это теперь недопустимо, чтобы вписать shapes в теле метода. Например, это не позволяется:

public void addRectangle(List<? extends Shape> shapes) {
    // Compile-time error!
    shapes.add(0, new Rectangle());
}

Следует быть в состоянии выяснить, почему код выше отвергается. Тип второго параметра к shapes.add() ? extends Shape- неизвестный подтип Shape. Так как мы не знаем то, что вводит это, мы не знаем, является ли это супертип Rectangle; это могло бы или не могло бы быть таким супертипом, таким образом, не безопасно передать a Rectangle там.

Ограниченные подстановочные знаки только, что нужно обработать пример DMV передача его данных в бюро переписи. Наш пример предполагает, что данные представляются, отображаясь с имен (представленный как строки) людям (представленный ссылочными типами такой как Person или его подтипы, такой как Driver). Map<K,V> пример универсального типа, который берет два параметра типа, представляя ключи и значения карты.

Снова, отметьте соглашение о присвоении имен формальными параметрами типа-K для ключей и V для значений.

public class Census {
    public static void addRegistry(Map<String, ? extends Person> registry) {
}
...

Map<String, Driver> allDrivers = ... ;
Census.addRegistry(allDrivers);

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

Предыдущая страница: Обобщения и Выделение подтипов
Следующая страница: Универсальные Методы



Spec-Zone.ru - all specs in one place