Обобщения Java GetThis Trick Объяснение - PullRequest
7 голосов
/ 24 декабря 2009

Я читаю о Java Generics и натолкнулся на эту тему, где я немного запутался.

От: http://www.angelikalanger.com/GenericsFAQ/FAQSections/ProgrammingIdioms.html#FAQ205

public abstract class Node <N extends Node<N>>  {
   private final List<N> children = new ArrayList<N>();
   private final N parent;

   protected Node(N parent) {
     this.parent = parent;
     parent.children.add(this);  // error: incompatible types
   }
   public N getParent() {
     return parent;
   }
   public List<N> getChildren() {
     return children;
   }
 }

public class SpecialNode extends Node<SpecialNode> {
   public SpecialNode(SpecialNode parent) {
     super(parent);
   }
} 

Прокрутка вниз на пару экранов ...

public abstract class Node <N extends Node<N>>  {
   ...
   protected Node(N parent) {
     this.parent = parent;
     parent.children.add( (N)this ); // warning: unchecked cast
   }
   ...
 }

Приведение типов с целевым типом параметр не может быть проверен в время выполнения и привести к неконтролируемой предупреждение. Этот небезопасный актерский состав вводит потенциал для неожиданного ClassCastException с и лучше избегать.

Может ли кто-нибудь привести пример, когда приведенный выше код вызывает исключение ClassCastException?

Спасибо.

1 Ответ

5 голосов
/ 24 декабря 2009

Первый пример кода

В первом примере кода есть ошибка компиляции. Вы можете проверить это самостоятельно в своей IDE.

Шахта говорит: The method add(N) in the type List<N> is not applicable for the arguments (Node<N>)

Проблема в том, что N является подтипом узла. Список N может быть списком StupidNode, где StupidNode является подклассом Node. Но текущий экземпляр может не являться StupidNode, это может быть другой подкласс Node, поэтому добавление может быть неправильным .


Второй пример кода

Теперь во втором примере кода разработчик, раздраженный ошибкой во время компиляции, которую он не понимает, считает, что компилятор ошибочен, и пытается вызвать приведение. Такое приведение приводит к компиляции кода, но может сломаться во время выполнения в тех же условиях (как объяснено выше).

Поэтому компилятор выдает предупреждение, чтобы помочь вам понять, что что-то может быть не так.


Пример задачи

Для обоих предыдущих примеров кода проблема может возникнуть, если код вызывающего кода записывается (для двух подклассов NodeA и NodeB из Node):

Node<NodeA> root = new NodeA<NodeA>(null); 
// code needs a change, to be able to handle the root, that has no parent
// The line with parent.children will crash with a NullPointerException
Node<NodeB> child = new NodeB<NodeB>(root);

Во второй строке код, который будет выполняться в конструкторе Node, будет интерпретироваться следующим образом (замена параметра формата N на текущий параметр NodeB):

public abstract class Node <NodeB>  {
   private final List<NodeB> children = new ArrayList<NodeB>();
   private final NodeB parent;

   protected Node(NodeB parent) {
     this.parent = parent;
     parent.children.add(this);  // error: incompatible types
   }
  // ...
 }

Как видите, вторая строка вызывающей стороны пройдет экземпляр NodeA, а конструктор Node ожидает NodeB! Отсюда и ошибка ...


ОБНОВЛЕНИЕ согласно запросу: пример кода для подклассов NodeA (или NodeB).

public class NodeA extends Node<NodeA>  {
   public NodeA(NodeA parent) {
     super(parent);
   }
}
...