15

I am building a generic Tree<T> class, which supports inheritance of sub-trees. But I've encountered some problems. Would you please kindly help me?

Description

Let's define the Tree class and the BlueTree class, where BlueTree extends Tree.

Let's define the Leaf class and the RedLeaf class, where RedLeaf extends Leaf. They are used as the "data" the Trees contain.

A Tree<Leaf> means a Tree of type Tree, and its "data" is of type Leaf.

For inheritance (this is not proper Java inheritance):

  • a Tree<Leaf> can have child of type
    • Tree<Leaf>, Tree<RedLeaf>, BlueTree<Leaf>, and BlueTree<RedLeaf>.

.

  • a Tree<RedLeaf> can have child of type
    • Tree<RedLeaf>, and BlueTree<RedLeaf>,
    • but not Tree<Leaf>, or BlueTree<Leaf>.

.

  • a BlueTree<Leaf> can have child of type
    • BlueTree<Leaf>, and BlueTree<RedLeaf>,
    • but not Tree<Leaf>, or Tree<RedLeaf>.

.

  • a BlueTree<RedLeaf> can have child of type
    • BlueTree<RedLeaf>,
    • but not Tree<Leaf>, Tree<RedLeaf>, or BlueTree<Leaf>.

*Here, "child" means the branches / leaves of the Tree.

(a bit complicated, that's why I separate the lines.)

The code

(If you have a solution, you may not need to read the verbose illustration of my attempts below. If you wish to find out the solution together, my code may give you some ideas - or, it may confuse them.)

First Trial: (the simple one)

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

This class structure meets most of the requirements in the description. Except, it allows

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

which violates

  • a BlueTree<Leaf> cannot have child of type Tree<Leaf>.

The problem is because, in BlueTree<Leaf>, its addChild() method signature is still

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

The ideal case is, the BlueTree<Leaf>.addChild() method signature is changed (automatically, upon inheritance) to

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

(Note that this method cannot override the above method by inheritance, as the parameter types differ.)

There is a workaround. We may add a class inheritance check, and throw RuntimeException for this case:

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
}

But making it a compile-time error is far better than a run-time error. I would like to enforce this behaviour at compile-time.

Second Trial

The problem in the first trial structure is, the parameter type Tree in the method addChild() is not a generic type parameter. Thus it will not be updated upon inheritance. This time, let's try to make it a generic type parameter also.

Firstly, define the general 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
    }
}

Then the TreeManager which manages a Tree object.

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

The TreeManager initialises with no problems; the lines are a bit long though. It conforms to the rules in the description also.

However, there is a compile-error when calling Tree.addChild() inside TreeManager, as illustrated above.

Third Trial

To fix the compile-error in the second trial, I tried changing the class signature (to even longer). Now mTree.addChild(subTree); compiles with no problems.

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

And I have tested it with very similar code as to the second trial. It creates without any problems, as the second trial does. (Just even longer.)

(You may skip the code block below, as it is just logically repeating.)

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

However, a problem arises when I try to call 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>)

This is understandable. TreeManager3.managerAddChild(NodeType) means TreeManager3.managerAddChild(Tree<T>) and there is no wildcard Tree<? extends T> in the parameter type, like Tree.addChild(final Tree<? extends T> subTree) in the first trial.

Begging for your help ...

I have already run out of ideas. Was I going in the wrong direction to solve this problem? I have spent a lot of time typing up this question and tried my greatest effort to make it more readable, easier to understand, and to follow. I have to say sorry that it is still very long and verbose. But could you please help if you know the way, or please give me any ideas you have? Your every input is highly appreciated. Thanks a lot!


Edit #1 (for the comment below)

Based in the First Trial, only allow mChildren to be modified by addChild() (and other methods with the isAssignableFrom() check), so even allowing user inheritance of Tree and overriding addChild() will not break the Tree integrity.

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

What do you think?

Community
  • 1
  • 1
midnite
  • 5,157
  • 7
  • 38
  • 52
  • did you consider your object to be Tree,LEAF> ? so in this case you will have type of you tree as well – user902383 Aug 23 '13 at 10:34
  • Thanks for your reply!! I must try it once i back home. If that works, it would be so great! And the simple, nice, and neat way :-) – midnite Aug 23 '13 at 11:40
  • @user902383, Thanks again. i have just tried in my eclipse. `public class Tree, T> { }` doesn't compile, sadly. – midnite Aug 23 '13 at 14:23
  • 1
    try `public class Tree,LEAF extends Leaf> { }` – user902383 Aug 23 '13 at 14:37
  • @user902383, Really THANKS a lot indeed!!! i guess your answer works! For now i tested it works as expected for the default constructor, the assignment (different type just not assignable, as expected), the `addChild()` method, and the `setParent()` method. i cant wait to build the entire Tree class soon! For reference, i used the signatures of `class Tree2, LEAF> { }`, `class BlueTree2,L> extends Tree2 { }`, `Tree2 super TREE, ? super LEAF> mParent;` and `void addChild(Tree2 extends TREE, ? extends LEAF> subTree) {}`. – midnite Aug 23 '13 at 18:47
  • 1
    no problem, gimme shout if you will have more problems with that – user902383 Aug 24 '13 at 16:06
  • Dear @user902383, if `class Tree, DATA> {}` and it has sub-classes `BigBlueTree extends BlueTree extends Tree`. While creating an object, one can actually do `Tree, String> tree = new Tree, String>();`, which makes `BigBlueTree` as the type argument of `Tree<>`, and (may) cause errors in the methods. **How to ensure users pass in exactly `Tree` in the `Tree<>` type argument?** – midnite Aug 31 '13 at 07:25
  • (cont'd) Conceptually, it would be nice if we can `class Tree, DATA> {}` (But `equals` is not a correct keyword). i've tried `class Tree, DATA> {}`, but i cannot (or don't know how to) create an object from this class. Thanks a lot a lot!! – midnite Aug 31 '13 at 07:29
  • is it any reason why you doing `Tree, String> tree` instead `BigBlueTree, String> tree`? – user902383 Sep 02 '13 at 09:20
  • @user902383, i mean the user may create the object wrongly, like that. If it is possible to enforce and prevent the user create it wrongly in that way, it would be nice. i think of two solutions, (1) pass a `Class` into the constructor for checking (but this will make the constructor more complicated), or (2) only allow modifying itself node and the child nodes in the public API, make all methods about the parent, like `attachParent()` private, i think such that we can ensure the structure integrity of the tree hierarchy. – midnite Sep 02 '13 at 11:43
  • i dont think there is easy way to obtain generic types, so unfortunately passing `Tree.class` to constructor might be right way to go. second thing is, as i understand by user you mean programmer, and you are developing library. in this case, i think you can assume he knows what he is doing. – user902383 Sep 02 '13 at 13:00
  • @user902383, Thanks for reply. Yes, that's a library. Yes, user = programmer. So you think i just add a "Usage Guide" in the class description is fine enough? – midnite Sep 02 '13 at 14:06
  • 1
    yes, use javadoc that should be enough, if this will be real problem, then you can add class parameter to your class, and use static factory to construct objects. but as i said, i dont see this as real problem – user902383 Sep 02 '13 at 14:11
  • Dear @user902383, Thanks for your helps so far. After a second thought, i decided to add the `Class<>` parameter in the constructor for checking. But there is still a problem while doing so. i have put it in a separated question. Would you please have a look? http://stackoverflow.com/questions/18581788/ – midnite Sep 02 '13 at 23:08
  • Dear @user902383, Thanks very much for your helps again. Why i think we have to enforce the check by code is, because the structure `Tree, DATA>` is very fragile, both while defining sub-classes, and object creation. Not to mention users may misuse or hack it, even it is tricky to myself that i may easily do some incorrect syntax (just like [Paul's answer](http://stackoverflow.com/a/7355094/1884546) said). i'm about to go back to use my very **First Trial**, with `isAssignableFrom()` check. Do you think it is a good idea? – midnite Sep 04 '13 at 19:24
  • i have a feeling, you are trying to get perfect solution, forgetting about that, you cant get perfect solution in imperfect world. In your solution from **First Trial** you can still override `addChild` method to avoid doing check. Just quick question, do you want to allow to users to create their own Trees? if yes, your solution might forbidd them to create tree which accepts every tree but not ie blue. If you dont want them to allow to create own trees, maybe you should consider builder pattern? – user902383 Sep 05 '13 at 08:40
  • @user902383, Thanks for reply! Yes i'd like to make my class as robust as i can. And yes, i would like to allow users to sub-class and make their own Trees. i think we can make `mChildren` private, so sub-classes must call through `super.addChild()` when adding a child. i think this can enforce the check. Please have a look at my added codes above. Thanks. – midnite Sep 05 '13 at 10:45
  • looks nice, and seems working, personally i'm not big fun doing that kind of checks during runtime. I think also good idea is to check is tree which you adding is not already added somewhere, so maybe your tree shoud have getParent method, which will be setted only when you adding them so tree – user902383 Sep 06 '13 at 10:17

3 Answers3

1

As I see it, there is no perfect solution to this problem. This is basically due to type erasure. The Erasure of Generic Methods article explains that your addChild(final Tree<? extends Leaf> subTree) function will become a addChild(final Tree subTree) function. So, even if you could somehow have a generic parameter <TreeType extends Tree<? extends Leaf>> addChild(final TreeType subTree) (not valid syntax!) it would be erased to addChild(final Tree subTree) at compile time. Adding your runtime test will work though, so the edit you made will do the job.

blalasaadri
  • 5,990
  • 5
  • 38
  • 58
0

I think what you need is the following

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
}
Keshava
  • 702
  • 1
  • 7
  • 20
0

did you try such code?

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