【问题标题】:Build A Generic Tree With Inheritance使用继承构建通用树
【发布时间】:2014-11-01 08:31:11
【问题描述】:

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

说明

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

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

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

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

  • Tree<Leaf> 可以有子类型
    • Tree<Leaf>Tree<RedLeaf>BlueTree<Leaf>BlueTree<RedLeaf>

.

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

.

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

.

  • BlueTree<RedLeaf> 可以有子类型
    • BlueTree<RedLeaf>,
    • 但不是Tree<Leaf>Tree<RedLeaf>BlueTree<Leaf>

*这里的“子”是指树的树枝/树叶。

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

代码

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

初审:(最简单的)

// 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&lt;Leaf&gt; 不能拥有Tree&lt;Leaf&gt; 类型的子级。

问题在于,在BlueTree&lt;Leaf&gt; 中,它的addChild() 方法签名仍然是

public void addChild(final Tree<? extends Leaf> subTree) {
     // add the subTree to mChildren
}

理想的情况是,BlueTree&lt;Leaf&gt;.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
}

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

二审

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

首先,定义通用的Tree类。

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

然后是管理Tree 对象的TreeManager

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 初始化没有问题;行虽然有点长。它也符合描述中的规则。

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

第三次试用

为了修复第二次试用中的编译错误,我尝试更改类签名(更长)。现在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&lt;T&gt;),参数类型中没有通配符Tree&lt;? extends T&gt;,如初审中的Tree.addChild(final Tree&lt;? extends T&gt; subTree)

请求您的帮助...

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


编辑 #1(用于comment below

基于一审,只允许mChildrenaddChild()修改(以及其他带有isAssignableFrom()检查的方法),所以即使允许用户继承Tree和覆盖 addChild() 不会破坏树的完整性。

/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.");
    }
}

你怎么看?

【问题讨论】:

  • 你认为你的对象是 Tree,LEAF> 吗?所以在这种情况下,你也会有你的树的类型
  • 感谢您的回复!!一旦我回到家,我必须尝试一下。如果这行得通,那就太好了!还有简单、漂亮、整洁的方式:-)
  • 试试public class Tree&lt;TREE extends Tree&lt;?,?&gt;,LEAF extends Leaf&gt; { }
  • 没问题,如果您对此有更多问题,请大声喊叫
  • 是的,使用javadoc就足够了,如果这真的是问题,那么你可以在你的类中添加类参数,并使用静态工厂来构造对象。但正如我所说,我不认为这是真正的问题

标签: java generics inheritance tree type-parameter


【解决方案1】:

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

【讨论】:

    【解决方案2】:

    我认为您需要的是以下内容

    class Tree<LT extends Leaf>{
    //have your generic add/delete/traverse methods here.
    }
    
    class BlueTree<LT extends Leaf> extends Tree<LT>{
    //have your blue tree specific add/delete/traverse methods here.
    }
    
    class Leaf {
    //have basic data members here
    }
    class BlueLeaf extends Leaf{
    //have blue leaf specific data members here
    }
    

    【讨论】:

      【解决方案3】:

      你试过这样的代码吗?

      package trees;                                                                                                          
      
      import java.util.ArrayList;                                                                                             
      
      public class Trees {                                                                                                    
      
          public static void main(String... args) {                                                                           
              Tree<Leaf, Tree<? extends Leaf, ?>> tree_leaf = new Tree<>();                                                   
              BlueTree<Leaf, BlueTree<? extends Leaf, ?>> blueTree_leaf = new BlueTree<>();                                   
              Tree<RedLeaf, Tree<? extends RedLeaf, ?>> tree_redLeaf = new Tree<>();                                          
              BlueTree<RedLeaf, BlueTree<? extends RedLeaf, ?>> blueTree_redLeaf = new BlueTree<>();                          
              //1                                                                                                             
              tree_leaf.addChild(tree_leaf);                                                                                  
              tree_leaf.addChild(tree_redLeaf);                                                                               
              tree_leaf.addChild(blueTree_leaf);                                                                              
              tree_leaf.addChild(blueTree_redLeaf);                                                                           
              //2                                                                                                             
              tree_redLeaf.addChild(tree_redLeaf);                                                                            
              tree_redLeaf.addChild(blueTree_redLeaf);                                                                        
              tree_redLeaf.addChild(tree_leaf);//compile error                                                                
              tree_redLeaf.addChild(blueTree_leaf);//compile error                                                            
              //3                                                                                                             
              blueTree_leaf.addChild(blueTree_leaf);                                                                          
              blueTree_leaf.addChild(blueTree_redLeaf);                                                                       
              blueTree_leaf.addChild(tree_leaf);//compile error                                                               
              blueTree_leaf.addChild(tree_redLeaf);//compile error                                                            
              //4                                                                                                             
              blueTree_redLeaf.addChild(blueTree_redLeaf);                                                                    
              blueTree_redLeaf.addChild(tree_leaf);//compile error                                                            
              blueTree_redLeaf.addChild(tree_redLeaf);//compile error                                                         
              blueTree_redLeaf.addChild(blueTree_leaf);//compile error                                                        
      
          }                                                                                                                   
      }                                                                                                                       
      
      class Tree<Data ,Children extends Tree<? extends Data, ?>> {                                                            
      
          //important in this question                                                                                        
          private Tree<? super Data, ? super Children> mParent;                                                               
          private Data mData;                                                                                                 
          private ArrayList<Children> mChildren;                                                                              
      
          // This is the focus of this question, the addChild() method signature                                              
          public void addChild(final Children subTree) {                                                                      
              // add the subTree to mChildren                                                                                 
          }                                                                                                                   
      
      }                                                                                                                       
      
      
      class BlueTree<Data, Children extends BlueTree<? extends Data, ?>> extends Tree<Data, Children> {                       
      }                                                                                                                       
      
      class Leaf {                                                                                                            
      }                                                                                                                       
      
      class RedLeaf extends Leaf {                                                                                            
      }                                                                                                                       
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-10-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多