如何在Java中处理自引用和继承

cod*_*dle 4 java generics inheritance

我希望字段"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...//

}
Run Code Online (Sandbox Code Playgroud)

当我尝试在这个类中调用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...//

}
Run Code Online (Sandbox Code Playgroud)

不幸的是,我不能只重写getChildren()函数,因为返回类型必须匹配.

iso*_*cte 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

打破这个分开

通过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());
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

DecidableTree关于实际值类型仍然是通用的,但它不是通用的T.它说a T是它自己的一个实例.这允许我们从父节点获取值而无需向下转换.

这将编译并正常工作,现在您可以直接根据泛型描述所有类型.注意我添加了一些方法,以便您可以进行一些有意义的测试.

最后一点

在你的榜样,我也随之变化Node是抽象的,ArrayListList无处不在,除了在其上实例化的路线.我假设你永远不会直接创建实例Node,所以它应该是abstract.至于ArrayList,这是最好的做法是指你的数据结构,通过该接口,在这种情况下List,而不是由实际执行(除非有一些非常具体的理由不).这使您可以通过仅更改一行代码来非常轻松地更改数据结构.