Java泛型GetThis技巧解释

7

我正在阅读有关Java泛型的内容,其中有一个主题让我有点困惑。

来自: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的例子吗?

谢谢。


你说得对,我没有仔细看,假设异常会在提取时抛出。已删除我的评论,因为它可能会误导。 - kdgregory
1个回答

5

第一个代码示例

在第一个代码示例中,存在一个编译错误。您可以在IDE中验证它。

我的提示是:List<N>中的方法add(N)不适用于参数(Node<N>)

问题在于N是Node的子类型。 N的列表可能是StupidNode的列表,其中StupidNode是Node的子类。 但是当前实例可能不是StupidNode,它可能是Node的另一个子类,因此将其添加可能是错误的


第二个代码示例

现在,第二个代码示例是一个开发人员由于无法理解的编译时错误而感到烦恼,认为编译器是错误的并尝试强制转换的示例。 这样的转换使代码编译,但在相同的条件下可能会在运行时出现问题(如上所述)。

因此,编译器发出警告,以帮助您了解某些问题可能出现。


示例问题

对于前两个代码示例,如果调用代码编写如下内容(对于NodeA和NodeB的两个子类NodeANodeB):

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

@java学生 对我来说,第二个代码只是替换了第一个无法编译的代码,并添加了一个强制转换使其能够编译。条件完全相同,只是不再在编译时出现错误,而是在运行时出现错误(ClassCastException)。 - KLE
@javaStudent 我在我的答案中添加了一个示例问题部分,以帮助你理解这个问题。请注意,这个问题对你写的两个代码样本是相同的,唯一的区别是它表现出来的方式(编译时错误或运行时 ClassCastException)。 - KLE
我尝试了以下内容,但运行时没有异常。public static void main(String[] args) { SpecialNode parent = new SpecialNode(null); SpecialNode child = new SpecialNode(parent); List<SpecialNode> children = child.getChildren(); for (SpecialNode specialNode : children) { // } } - javaStudent
2
@KLE - 离题了,但我认为圣诞节是一个合理的延迟,不需要道歉。 :-) - Andrzej Doyle
@KLE 你好,抱歉我来晚了。但是你能否展示一下如何保护NodeA被传递到new NodeB<NodeB>(root);的参数中呢?是否有一种模式或设计可以确保安全性?提前感谢。 - Unheilig
显示剩余4条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接