Ответ 1
В вашем примере вы можете использовать простой List<Shape>
, как сказал Дэн и Пол; вам не нужно использовать синтаксис вопросительных знаков подстановки, например List<? super Shape>
или List<? extends Shape>
). Я думаю, что ваш основной вопрос может быть следующим: "Когда я буду использовать одну из деклараций стиля вопросительных знаков?" (Принцип "Get and Put", который цитирует Жюльен, является отличным ответом на этот вопрос, но я не думаю, что это имеет смысл, если вы не видите его в контексте примера.) Здесь мой взгляд на расширенную версию Get и Положите принцип, когда следует использовать подстановочные знаки.
Используйте <? extends T>
, если...
- Метод имеет общий класс
параметр
Foo<T>
readSource - Метод GETS экземпляров T из readSource, и не имеет значения, принадлежит ли реальный объект к подклассу T.
Используйте <? super T>
, если...
- Метод имеет общий параметр класса
Foo<T>
writeDest - Метод PUTS экземпляров T в writeDest, и не имеет значения, если writeDest также содержит объекты, которые являются подклассами T.
Здесь приведено описание конкретного примера, иллюстрирующего мышление под шаблонами. Представьте, что вы пишете метод processSquare, который удаляет квадрат из списка, обрабатывает его и сохраняет результат в списке вывода. Здесь подпись метода:
void processSquare(List<Square> iSqua, List<Square> oSqua)
{ Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); }
Теперь вы создаете список DoubleSquares, которые расширяют Square и пытаются их обработать:
List<DoubleSquare> dsqares = ...
List<Square> processed = new ArrayList<Square>;
processSquare(dsqares, processed); // compiler error! dsquares is not List<Square>
Сбой компилятора с ошибкой, потому что тип dsquares List<DoubleSquare>
не соответствует типу первого параметра processSquare, List<Square>
. Возможно, DoubleSquare - это квадрат, но вам нужно сообщить компилятору, что List<DoubleSquare>
is-a List<Square>
для целей вашего метода processSquare. Используйте подстановочный знак <? extends Square>
, чтобы сообщить компилятору, что ваш метод может принимать список любого подкласса Square.
void processSquare(List<? extends Square> iSqua, List<Square> oSqua)
Затем вы улучшаете приложение для обработки кругов, а также квадратов. Вы хотите объединить все обработанные фигуры в один список, который включает в себя круги и квадраты, поэтому вы изменили тип обработанного списка с List<Square>
на List<Shape>
:
List<DoubleSquare> dsqares = ...
List<Circle> circles = ...
List<Shape> processed = new ArrayList<Square>;
processSquare(dsqares, processed); // compiler error! processed is not List<Square>
Сбой компилятора с новой ошибкой. Теперь тип обработанного списка List<Shape>
не соответствует второму параметру processSquare, List<Square>
. Используйте подстановочный знак <? super Square>
, чтобы сообщить компилятору, что данный параметр может быть списком любого суперкласса Square.
void processSquare(List<? extends Square> iSqua,
List<? super Square> oSqua)
Здесь приведен полный исходный код для примера. Иногда мне легче учиться, начиная с рабочего примера, а затем разбивая его, чтобы увидеть, как компилятор реагирует.
package wild;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public abstract class Main {
// In processing the square,
// I'll take for input any type of List that can PRODUCE (read) squares.
// I'll take for output any type of List that can ACCEPT (write) squares.
static void processSquare(List<? extends Square> iSqua, List<? super Square> oSqua)
{ Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); }
static void processCircle(List<? extends Circle> iCirc, List<? super Circle> oCirc)
{ Circle c = iCirc.remove(0); c.doCircle(); oCirc.add(c); }
public static void main(String[] args) {
// Load some inputs
List<Circle> circles = makeList(new Circle());
List<DoubleSquare> dsqares = makeList(new DoubleSquare());
// Collated storage for completed shapes
List<Shape> processed = new ArrayList<Shape>();
// Process the shapes
processSquare(dsqares, processed);
processCircle(circles, processed);
// Do post-processing
for (Shape s : processed)
s.shapeDone();
}
static class Shape { void shapeDone() { System.out.println("Done with shape."); } }
static class Square extends Shape { void doSquare() { System.out.println("Square!"); } }
static class DoubleSquare extends Square {}
static class Circle extends Shape { void doCircle() { System.out.println("Circle!"); } }
static <T> List<T> makeList(T a) {
List<T> list = new LinkedList<T>(); list.add(a); return list;
}
}