|
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);