Дженерики..? Супер Т

Возможный дубликат:
в чем разница между расширениями "super и" в Java Generics

A)

List<? super Shape> shapeSuper = new ArrayList<Shape>();

shapeSuper.add(new Square());       //extends from SHAP  
shapeSuper.add(new DoubleSquare()); //extends from SQ  
shapeSuper.add(new TripleSquare()); //extends from DS  
shapeSuper.add(new Rectangle());    //extends from SHAP  
shapeSuper.add(new Circle());       //extends from SHAP  

for (Object object : shapeSuper) { ... }

Зачем нужна итерация объектов, когда я могу добавить только Shape и ее производные?

В)

List<? super Shape> shapeSuper = new ArrayList<Object>();  

shapeSuper.add(new Object()); //compilation error  

Почему приведенная выше строка создает ошибку компиляции?

Ответ 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; 
  }

}

Ответ 2

Чтобы расширить на ответ Paul, объявив shapeSuper как List <? super Shape > , вы говорите, что он может принимать любой объект, который является супер-классом Shape. Объект - это суперкласс формы. Это означает, что общим суперклассом каждого из элементов списка является Object.

Вот почему вы должны использовать тип объекта в цикле for. Что касается компилятора, список может содержать объекты, которые не являются фигурами.

Ответ 3

"Принцип" Get and Put "в разделе (2.4) является реальной жемчужиной от Java Generics and Collections:

Принцип Get and Put: используйте расширяет подстановочный знак, когда вы получаете значения из структуры, используйте super подстановочный знак, когда вы только ставите значения в структуру и не использовать подстановочный знак когда вы оба получаете и ставите.

alt text http://oreilly.com/catalog/covers/0596527756_cat.gif

Кроме того, объявление типа List<? super Shape> shapeSuper является видом плохой формы, поскольку оно ограничивает его использование. Как правило, единственный раз, когда я использую wild-карты, есть подписи методов:

public void foo(List<? super Shape> shapeSuper)

Ответ 4

Попробуйте объявить shapeSuper как List<Shape>. Затем вы можете сделать

for (Shape shape : shapeSuper)

Ответ 5

А)

Потому что super указывает нижний ограничивающий класс общего элемента. Таким образом, List<? super Shape> может представлять List<Shape> или List<Object>.

В)

Поскольку компилятор не знает, что такое фактический тип List<? super Shape>.

Ваше добавление объекта с shapeSuper.add(new Object());, но компилятор знает только, что общий тип List является супер-типом Shape, но не знает точно, что это такое.

В вашем примере List<? super Shape> действительно может быть List<ShapeBase> заставлять компилятор запретить операцию shapeSuper.add(new Object());.

Помните, Дженерики не ковариантны.

Ответ 6

(Отказ от ответственности: я никогда не использовал "супер" в качестве универсального подстановочного знака, поэтому возьмите это с солью...)

Для (A) на самом деле вы не можете добавить Shape и его производные, вы можете добавить Shape и его предков. Я думаю, что, возможно, вы хотите

List<? extends Shape> shapeSuper = new ArrayList<Shape>();

Задание "extends" означает Shape и все, что происходит от Shape. Указание "супер" означает "Форма" и "Любая форма".

Не уверен в (B), если объект не является неявным. Что произойдет, если вы явно объявите форму как public class Shape extends Object?

Ответ 7

В отношении вышеизложенного я не думаю, что это правильно:

объявив shapeSuper как List<? super Shape> shapeSuper, вы говорите, что это может принять любой объект, который является супер класс формы

Это кажется интуитивным на первый взгляд, но на самом деле я не думаю, что это работает. Вы не можете вставлять только суперкласс Shape в shapeSuper. superShape на самом деле является ссылкой на список, который может быть ограничен наличием определенного (но неуказанного) супертипа формы.

Позволяет представить, что Shape реализует Viewable и Drawable. Поэтому в этом случае ссылка superShape может фактически указывать на List<Viewable> или List<Drawable> (или действительно a List<Object>), но мы не знаем, какой из них. Если на самом деле это List<Viewable>, вы не сможете вставить в него экземпляр Drawable, и компилятор помешает вам это сделать.

Конструкция нижней границы по-прежнему очень полезна для того, чтобы сделать универсальные классы более гибкими. В следующем примере это позволяет нам перейти к методу addShapeToSet a Set, определенному для любого суперкласса Shape, и мы все равно можем вставить в него Shape:

public void addShapeToSet(Set<? super Shape> set) {
    set.add(new Shape());
}

public void testAddToSet() {
    //these all work fine, because Shape implements all of these:
    addShapeToSet(new HashSet<Viewable>());
    addShapeToSet(new HashSet<Drawable>());           
    addShapeToSet(new HashSet<Shape>());           
    addShapeToSet(new HashSet<Object>());
}