Чтобы ответить на этот вопрос, мне нужно объяснить неограниченные символы и ограниченные символы.
Содержание этого поста было собрано из документации Java.
1. Неограниченные символы
Неограниченный тип подстановочного знака указывается с использованием подстановочного знака (?
), например, List<?>
. Это называется списком неизвестного типа. Существует два сценария, в которых неограниченный подстановочный знак является полезным подходом:
Если вы пишете метод, который может быть реализован с использованием функций, предоставляемых в классе Object.
Когда код использует методы в универсальном классе, которые не зависят от параметра типа. Например, List.size
или List.clear
. На самом деле, Class<?>
используется так часто, потому что большинство методов в Class<T>
не зависят от T
.
2. Ограниченные подстановочные знаки
Рассмотрим простое приложение для рисования, которое может рисовать такие фигуры, как прямоугольники и круги. Чтобы представить эти фигуры в программе, вы можете определить иерархию классов, например:
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: его нельзя, например, вызывать для List<Circle>
. Это прискорбно, так как все, что делает метод - это читает фигуры из списка, поэтому его можно также вызывать для List<Circle>
. Что нам действительно нужно, так это чтобы метод принимал список любой формы:
public void drawAll (Список фигур) {
...
}
Здесь есть небольшое, но очень важное отличие: мы заменили тип List<Shape>
на List<? extends Shape>
. Теперь drawAll()
будет принимать списки любого подкласса Shape
, поэтому теперь мы можем вызвать его на List<Circle>
, если захотим.
List<? extends Shape>
является примером ограниченного символа подстановки. ?
обозначает неизвестный тип, однако в этом случае мы знаем, что этот неизвестный тип на самом деле является подтипом Shape. (Примечание: это может быть сама форма или некоторый подкласс; ей не нужно буквально расширять форму.) Мы говорим, что форма - это верхняя граница подстановочного знака.
Аналогично, синтаксис ? super T
, который является ограниченным подстановочным знаком, обозначает неизвестный тип, который является супертипом T.
A ArrayedHeap280<? super Integer>
, например, включает в себя ArrayedHeap280<Integer>
, ArrayedHeap280<Number>
и ArrayedHeap280<Object>
.
Как вы можете видеть в документации Java для класса Integer , Integer является подклассом Number, который, в свою очередь, является подклассом Object.