java - 使用继承构建通用树

标签 java generics inheritance tree type-parameter

我正在构建一个通用的 Tree<T>类,支持子树的继承。但是我遇到了一些问题。你能帮我吗?

描述

让我们定义 Tree类和 BlueTree类,其中 BlueTree extends Tree .

让我们定义 Leaf类和 RedLeaf类,其中 RedLeaf extends Leaf .它们用作树包含的“数据”。

一个 Tree<Leaf>表示 Tree 类型的树,其“数据”的类型为 Leaf .

继承 (这不是正确的 Java 继承):

  • Tree<Leaf>可以有类型的 child
  • Tree<Leaf> , Tree<RedLeaf> , BlueTree<Leaf> , 和 BlueTree<RedLeaf> .

  • .
  • Tree<RedLeaf>可以有类型的 child
  • Tree<RedLeaf> , 和 BlueTree<RedLeaf> ,
  • 但不是 Tree<Leaf> , 或 BlueTree<Leaf> .

  • .
  • BlueTree<Leaf>可以有类型的 child
  • BlueTree<Leaf> , 和 BlueTree<RedLeaf> ,
  • 但不是 Tree<Leaf> , 或 Tree<RedLeaf> .

  • .
  • BlueTree<RedLeaf>可以有类型的 child
  • BlueTree<RedLeaf> ,
  • 但不是 Tree<Leaf> , Tree<RedLeaf> , 或 BlueTree<Leaf> .

  • *这里,“ child ”是指树的 Twig /叶子。

    (有点复杂,这就是我分开行的原因。)

    编码

    (如果你有一个解决方案,你可能不需要阅读下面我尝试的详细说明。如果你想一起找出解决方案,我的代码可能会给你一些想法 - 或者,它可能会混淆它们。)

    初审 :(最简单的一个)
    // This is the focus of this question, the class signature
    public class Tree<T> {
        // some fields, but they are not important in this question
        private Tree<? super T> mParent;
        private T mData;
        private ArrayList<Tree<? extends T>> mChildren;
    
        // This is the focus of this question, the addChild() method signature
        public void addChild(final Tree<? extends T> subTree) {
            // add the subTree to mChildren
        }
    }
    

    这个类结构满足描述中的大部分要求。除了,它允许
    class BlueTree<T> extends Tree<T> { }
    class Leaf { }
    class RedLeaf extends Leaf { }
    
    Tree<Leaf> tree_leaf = new Tree<Leaf>();
    BlueTree<Leaf> blueTree_leaf = new BlueTree<Leaf>();
    
    blueTree_leaf.addChild(tree_leaf);    // should be forbidden
    

    这违反了
  • BlueTree<Leaf> 不能 Tree<Leaf> 类型的 child .

  • 问题是因为,在 BlueTree<Leaf> ,其 addChild()方法签名还在
    public void addChild(final Tree<? extends Leaf> subTree) {
         // add the subTree to mChildren
    }
    

    理想的情况是,BlueTree<Leaf>.addChild()方法签名更改(自动,继承时)为
    public void addChild(final BlueTree<? extends Leaf> subTree) {
         // add the subTree to mChildren
    }
    

    (注意这个方法不能通过继承覆盖上面的方法,因为参数类型不同。)

    有一个解决方法。我们可能会添加一个类继承检查,并抛出 RuntimeException对于这种情况:
    public void addChild(final Tree<? extends Leaf> subTree) {
        if (this.getClass().isAssignableFrom(subTree.getClass()))
            throw new RuntimeException("The parameter is of invalid class.");
        // add the subTree to mChildren
    }
    

    但是使它成为编译时错误比运行时错误要好得多。我想在编译时强制执行此行为。

    二审

    第一个试验结构的问题是,参数类型Tree在方法中 addChild()不是泛型类型参数。因此它不会在继承时更新。这一次,让我们也试着让它成为一个泛型类型参数。

    首先定义通用Tree类(class)。
    public class Tree<T> {
        private Tree<? super T> mParent;
        private T mData;
        private ArrayList<Tree<? extends T>> mChildren;
    
        /*package*/ void addChild(final Tree<? extends T> subTree) {
            // add the subTree to mChildren
        }
    }
    

    然后是TreeManager管理一个 Tree目的。
    public final class TreeManager<NodeType extends Tree<? super DataType>, DataType> {
        private NodeType mTree;
    
        public TreeManager(Class<NodeType> ClassNodeType) {
            try {
                mTree = ClassNodeType.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public void managerAddChild(final NodeType subTree) {
            mTree.addChild(subTree);
            // compile error: The method addChild(Tree<? extends capture#1-of ? super DataType>)
            //                in the type Tree<capture#1-of ? super DataType>
            //                is not applicable for the arguments (NodeType)
        }
    
        // for testing
        public static void main(String[] args) {
            @SuppressWarnings("unchecked")
            TreeManager<Tree    <Leaf>   , Leaf>    tm_TreeLeaf_Leaf           = new TreeManager<Tree    <Leaf>,    Leaf>   ((Class<Tree    <Leaf>>)    new Tree    <Leaf>   ().getClass());
            TreeManager<Tree    <RedLeaf>, RedLeaf> tm_TreeRedLeaf_RedLeaf     = new TreeManager<Tree    <RedLeaf>, RedLeaf>((Class<Tree    <RedLeaf>>) new Tree    <RedLeaf>().getClass());
            TreeManager<BlueTree<Leaf>   , Leaf>    tm_BlueTreeLeaf_Leaf       = new TreeManager<BlueTree<Leaf>,    Leaf>   ((Class<BlueTree<Leaf>>)    new BlueTree<Leaf>   ().getClass());
            TreeManager<BlueTree<RedLeaf>, RedLeaf> tm_BlueTreeRedLeaf_RedLeaf = new TreeManager<BlueTree<RedLeaf>, RedLeaf>((Class<BlueTree<RedLeaf>>) new BlueTree<RedLeaf>().getClass());
    
            System.out.println(tm_TreeLeaf_Leaf          .mTree.getClass());    // class Tree
            System.out.println(tm_TreeRedLeaf_RedLeaf    .mTree.getClass());    // class Tree
            System.out.println(tm_BlueTreeLeaf_Leaf      .mTree.getClass());    // class BlueTree
            System.out.println(tm_BlueTreeRedLeaf_RedLeaf.mTree.getClass());    // class BlueTree
    
            @SuppressWarnings("unchecked")
            TreeManager<Tree    <Leaf>   , RedLeaf> tm_TreeLeaf_RedLeaf     = new TreeManager<Tree    <Leaf>,    RedLeaf>((Class<Tree    <Leaf>>)    new Tree    <Leaf>   ().getClass());
            TreeManager<BlueTree<Leaf>   , RedLeaf> tm_BlueTreeLeaf_RedLeaf = new TreeManager<BlueTree<Leaf>,    RedLeaf>((Class<BlueTree<Leaf>>)    new BlueTree<Leaf>   ().getClass());
    
            System.out.println(tm_TreeLeaf_RedLeaf       .mTree.getClass());    // class Tree
            System.out.println(tm_BlueTreeLeaf_RedLeaf   .mTree.getClass());    // class BlueTree
    
            // the following two have compile errors, which is good and expected.
            TreeManager<Tree    <RedLeaf>, Leaf>    tm_TreeRedLeaf_Leaf     = new TreeManager<Tree    <RedLeaf>, Leaf>   ((Class<Tree    <RedLeaf>>) new Tree    <RedLeaf>().getClass());
            TreeManager<BlueTree<RedLeaf>, Leaf>    tm_BlueTreeRedLeaf_Leaf = new TreeManager<BlueTree<RedLeaf>, Leaf>   ((Class<BlueTree<RedLeaf>>) new BlueTree<RedLeaf>().getClass());
        }
    }
    
    TreeManager初始化没有问题;虽然线路有点长。它也符合描述中的规则。

    但是,在调用 Tree.addChild() 时会出现编译错误。内TreeManager ,如上图所示。

    三审

    为了修复第二次试验中的编译错误,我尝试更改类签名(甚至更长)。现在 mTree.addChild(subTree);编译没有问题。
    // T is not used in the class. T is act as a reference in the signature only
    public class TreeManager3<T, NodeType extends Tree<T>, DataType extends T> {
        private NodeType mTree;
    
        public TreeManager3(Class<NodeType> ClassNodeType) {
            try {
                mTree = ClassNodeType.newInstance();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public void managerAddChild(final NodeType subTree) {
            mTree.addChild(subTree);    // compile-error is gone
        }
    }
    

    我已经用与第二次试验非常相似的代码对其进行了测试。它没有任何问题,就像第二次审判一样。 (甚至更长。)

    (您可以跳过下面的代码块,因为它只是在逻辑上重复。)
    public static void main(String[] args) {
        @SuppressWarnings("unchecked")
        TreeManager3<Leaf   , Tree    <Leaf>   , Leaf>    tm_TreeLeaf_Leaf           = new TreeManager3<Leaf   , Tree    <Leaf>,    Leaf>   ((Class<Tree    <Leaf>>)    new Tree    <Leaf>   ().getClass());
        TreeManager3<RedLeaf, Tree    <RedLeaf>, RedLeaf> tm_TreeRedLeaf_RedLeaf     = new TreeManager3<RedLeaf, Tree    <RedLeaf>, RedLeaf>((Class<Tree    <RedLeaf>>) new Tree    <RedLeaf>().getClass());
        TreeManager3<Leaf   , BlueTree<Leaf>   , Leaf>    tm_BlueTreeLeaf_Leaf       = new TreeManager3<Leaf   , BlueTree<Leaf>,    Leaf>   ((Class<BlueTree<Leaf>>)    new BlueTree<Leaf>   ().getClass());
        TreeManager3<RedLeaf, BlueTree<RedLeaf>, RedLeaf> tm_BlueTreeRedLeaf_RedLeaf = new TreeManager3<RedLeaf, BlueTree<RedLeaf>, RedLeaf>((Class<BlueTree<RedLeaf>>) new BlueTree<RedLeaf>().getClass());
    
        System.out.println(tm_TreeLeaf_Leaf          .mTree.getClass());    // class Tree
        System.out.println(tm_TreeRedLeaf_RedLeaf    .mTree.getClass());    // class Tree
        System.out.println(tm_BlueTreeLeaf_Leaf      .mTree.getClass());    // class BlueTree
        System.out.println(tm_BlueTreeRedLeaf_RedLeaf.mTree.getClass());    // class BlueTree
    
        @SuppressWarnings("unchecked")
        TreeManager3<Leaf   , Tree    <Leaf>   , RedLeaf> tm_TreeLeaf_RedLeaf     = new TreeManager3<Leaf   , Tree    <Leaf>,    RedLeaf>((Class<Tree    <Leaf>>)    new Tree    <Leaf>   ().getClass());
        TreeManager3<Leaf   , BlueTree<Leaf>   , RedLeaf> tm_BlueTreeLeaf_RedLeaf = new TreeManager3<Leaf   , BlueTree<Leaf>,    RedLeaf>((Class<BlueTree<Leaf>>)    new BlueTree<Leaf>   ().getClass());
    
        System.out.println(tm_TreeLeaf_RedLeaf       .mTree.getClass());    // class Tree
        System.out.println(tm_BlueTreeLeaf_RedLeaf   .mTree.getClass());    // class BlueTree
    
        // the following two have compile errors, which is good and expected.
        TreeManager3<RedLeaf, Tree    <RedLeaf>, Leaf>    tm_TreeRedLeaf_Leaf     = new TreeManager3<RedLeaf, Tree    <RedLeaf>, Leaf>   ((Class<Tree    <RedLeaf>>) new Tree    <RedLeaf>().getClass());
        TreeManager3<RedLeaf, BlueTree<RedLeaf>, Leaf>    tm_BlueTreeRedLeaf_Leaf = new TreeManager3<RedLeaf, BlueTree<RedLeaf>, Leaf>   ((Class<BlueTree<RedLeaf>>) new BlueTree<RedLeaf>().getClass());
    }
    

    但是,当我尝试拨打 TreeManager3.managerAddChild() 时出现问题。 .
    tm_TreeLeaf_Leaf.managerAddChild(new Tree<Leaf>());
    tm_TreeLeaf_Leaf.managerAddChild(new Tree<RedLeaf>());      // compile error: managerAddChild(Tree<RedLeaf>) cannot cast to managerAddChild(Tree<Leaf>)
    tm_TreeLeaf_Leaf.managerAddChild(new BlueTree<Leaf>());
    tm_TreeLeaf_Leaf.managerAddChild(new BlueTree<RedLeaf>());  // compile error: managerAddChild(BlueTree<RedLeaf>) cannot cast to managerAddChild(BlueTree<Leaf>)
    

    这是可以理解的。 TreeManager3.managerAddChild(NodeType)意味着 TreeManager3.managerAddChild(Tree<T>)并且没有通配符 Tree<? extends T>在参数类型中,如 Tree.addChild(final Tree<? extends T> subTree)在第一次审判中。

    恳求你的帮助...

    我已经没有想法了。我是不是走错了方向来解决这个问题?我花了很多时间输入这个问题,并尽我最大的努力使它更具可读性、更易于理解和遵循。我不得不说对不起,它仍然很长很冗长。但是,如果您知道路,请您帮忙,或者请给我您的任何想法?非常感谢您的每一个意见。非常感谢!

    编辑 #1(对于 comment below )

    位于初审 , 只允许 mChildrenaddChild() 修改(以及其他带有 isAssignableFrom() 检查的方法),因此甚至允许用户继承 Tree和覆盖 addChild()不会破坏 Tree 的完整性。

    /developer/util/Tree.java
    package developer.util;
    
    import java.util.ArrayList;
    
    public class Tree<T> {
    
        private Tree<? super T> mParent;
        private final ArrayList<Tree<? extends T>> mChildren = new ArrayList<Tree<? extends T>>();
    
        public int getChildCount() { return mChildren.size(); }
        public Tree<? extends T> getLastChild() { return mChildren.get(getChildCount()-1); }
    
        public void addChild(final Tree<? extends T> subTree) {
            if (this.getClass().isAssignableFrom(subTree.getClass()) == false)
                throw new RuntimeException("The child (subTree) must be a sub-class of this Tree.");
    
            subTree.mParent = this;
            mChildren.add(subTree);
        }
    }
    

    /user/pkg/BinaryTree.java
    package user.pkg;
    
    import developer.util.Tree;
    
    public class BinaryTree<T> extends Tree<T> {
        @Override
        public void addChild(final Tree<? extends T> subTree) {
            if (getChildCount() < 2) {
                super.addChild(subTree);
            }
        }
    }
    

    /Main.java
    import user.pkg.BinaryTree;
    import developer.util.Tree;
    
    public class Main {
    
        public static void main(String[] args) {
            Tree<Integer> treeOfInt = new Tree<Integer>();
            BinaryTree<Integer> btreeOfInt = new BinaryTree<Integer>();
    
            treeOfInt.addChild(btreeOfInt);
            System.out.println(treeOfInt.getLastChild().getClass());
            // class user.pkg.BinaryTree
    
            try {
                btreeOfInt.addChild(treeOfInt);
            } catch (Exception e) {
                System.out.println(e);
                // java.lang.RuntimeException: The child (subTree) must be a sub-class of this Tree.
            }
    
            System.out.println("done.");
        }
    }
    

    你怎么认为?

    最佳答案

    在我看来,这个问题没有完美的解决方案。这基本上是由于类型删除。 Erasure of Generic Methods文章说明您的 addChild(final Tree<? extends Leaf> subTree)函数将变成 addChild(final Tree subTree)功能。所以,即使你能以某种方式拥有一个通用参数 <TreeType extends Tree<? extends Leaf>> addChild(final TreeType subTree) (无效的语法!)它将被删除为 addChild(final Tree subTree)在编译时。添加您的运行时测试将起作用,因此您所做的编辑将完成这项工作。

    关于java - 使用继承构建通用树,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18399922/

    相关文章:

    generics - 针对F <out X>的特定情况F <Nothing>缺少HKT的解决方法

    Java 通用函数 : Define a set of classes generic object must be

    c# - 使用 Vector2 和 Vector2 作为通用约束?

    Java 通用容器类

    C++ 在 { 标记之前期望类名,继承错误

    java - 如何编写哈希的删除方法来删除值?

    java - 从按钮调用时鼠标事件等待卡住

    java - 面向特定环境建模的 OOP 概念?

    java - 如何通过API取消paypal支付

    android - Instanceof 不返回正确答案