Spec-Zone .ru
спецификации, руководства, описания, API
|
Рассмотрите проблему записи подпрограммы, которая распечатывает все элементы в наборе. Вот то, как Вы могли бы записать это в более старой версии языка (то есть, пред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);