我希望字段“children”包含具有包含对象类型的对象列表。但是,当继承时,由于向下转换,我会收到错误。我可以使用哪些策略来保持层次结构,但能够适当地访问子对象?
public class Node<T>{
protected T value;
protected Node<T> parent;
private ArrayList<Node<T>> children;
public Node(T value){
this.value = value;
this.children = new ArrayList<Node<T>>();
}
public ArrayList<Node<T>> getChildren(){
return this.children;
}
//...other methods...//
}
当我尝试在此类中调用getChildren()时,出现“类型不匹配”错误,因为它正在尝试向下转换。
public class DecidableTree<T extends Decidable<T>> extends Node<T>{
public DecidableTree(T value) {
super(value);
}
public randomInvolvedFunction(){
//...other code...//
for(DecidableTree<T> child : this.getChildren()){
child.decidableTreeSpecificMethod();
}
//...other code...//
}
//...other methods...//
}
不幸的是,我不能只重写getChildren()函数,因为返回类型必须匹配。
答案 0 :(得分:11)
您遇到的问题是您确实在倾向,您试图将Node
的任何给定实例视为DecidableTree
。虽然您确实可以将DecidableTree
的任何实例视为Node
,因为它继承自Node
,但这种方法并不相反。
您当然可以使用instanceOf
运算符直接检查实例的类型是否正确,但有一种更简洁的方法可以执行此操作。
您可以通过将Node
类参数化为两个通用值来实现此目的。一个V
用于保存在给定Node
的值,另一个T
表示Node
的实际具体实例。
例如,
public abstract class Node<T, V> {
protected V value;
protected Node<T,V> parent;
private List<T> children;
public Node(V value){
this.value = value;
this.children = new ArrayList<T>();
}
public List<T> getChildren(){
return this.children;
}
public void addChild(T child){
this.children.add(child);
}
public V getVal(){
return this.value;
}
}
通过额外的类型变量Node
对T
进行参数化,这允许我们在父类中返回给定具体类型的值,而不会实际知道具体类型是什么。如果这仍然令人困惑,考虑到DecidableTree
的新实例可能会帮助您了解正在发生的事情,
public class DecidableTree<V> extends Node<DecidableTree<V>, V> {
public DecidableTree(V value) {
super(value);
}
public void randomInvolvedFunction(){
for(DecidableTree<V> child : this.getChildren()){
System.out.println(child.getVal());
}
}
}
对于实际值类型, DecidableTree
仍然是通用的,但对T
而言,它不是通用的。它说T
是它自己的一个实例。这允许我们从父节点获取值而不需要向下转换。
这将编译并正常工作,现在您可以根据泛型直接描述所有类型。注意我添加了一些方法,以便您可以进行一些有意义的测试。
在您的示例中,除了实例化的行之外,我还将Node
更改为抽象,将ArrayList
更改为List
。我假设您永远不会直接创建Node
的实例,因此它应该是abstract
。就ArrayList
而言,最佳做法是通过接口引用您的数据结构,在本例中为List
,而不是通过实际实现(除非有一些非常具体的原因) 。这样,您只需更改一行代码即可轻松更改 very 的数据结构。