0

AIM

For instance, we have a Tree class of type T: i.e. Tree<T>.

We would like to make this Tree<T> class to be able to hold

  1. Tree<T> (of course),
  2. SubTree<T> where SubTree extends Tree,
  3. Tree<SubT> where SubT extends T, and
  4. SubTree<SubT> where SubTree extends Tree and SubT extends T.

"Hold" means it accept the certain sub-class, and it returns the object of certain sub-class respectively, upon request.

For example, the original ArrayList has this property:

private static class Leaf {
}
private static class RedLeaf extends Leaf {
}
@Test
public final void test() {
    ArrayList<Leaf> al = new ArrayList<Leaf>();
    al.add(new Leaf());
    System.out.println(al.get(al.size()-1).getClass());     // class Leaf
    al.add(new RedLeaf());
    System.out.println(al.get(al.size()-1).getClass());     // class RedLeaf
}

It is because the original ArrayList just keep a reference of the input object, but not re-creating it. This is not a desired behaviour while building my class, especially a tree. Consider the following example:

public final void test() {
    ArrayList<Leaf> al = new ArrayList<Leaf>();
    Leaf leaf = new Leaf();
    RedLeaf redLeaf = new RedLeaf();
    al.add(leaf);
    al.add(redLeaf);
    al.add(leaf);
    System.out.println(al.indexOf(al.get( 0 )));    // 0
    System.out.println(al.indexOf(al.get( 1 )));    // 1
    System.out.println(al.indexOf(al.get( 2 )));    // 0 <-- disaster
}

Why this is a disaster? Consider for a certain node in a tree, we want to find the next sibling.

In fact, we can do a quick fix while inserting the elements:

private static class Leaf {
    Leaf() { super(); }
    Leaf(Leaf leaf) { super(); }
}
private static class RedLeaf extends Leaf {
    RedLeaf() { super(); }
    RedLeaf(RedLeaf redLeaf) { super(redLeaf); }
}
@Test
public final void test() {
    ArrayList<Leaf> al = new ArrayList<Leaf>();
    Leaf leaf = new Leaf();
    RedLeaf redLeaf = new RedLeaf();
    al.add(new Leaf(leaf));
    al.add(new RedLeaf(redLeaf));
    al.add(new Leaf(leaf));
    System.out.println(al.indexOf(al.get( 0 )));    // 0
    System.out.println(al.indexOf(al.get( 1 )));    // 1
    System.out.println(al.indexOf(al.get( 2 )));    // 2 <-- nice :-)
}

But when it comes to building our own class (of the Tree), this becomes a big problem.

So, our aims are:

  1. holding "all" the sub-classes, and
  2. having every element in the structure unique.

(solution below)


The original question

We have a Tree class, which uses ArrayList to hold the nodes:

public class Tree<T> {
    // some constructors & methods skipped
    private final ArrayList<Tree<T>> mChildren = new ArrayList<Tree<T>>();
}

We have this addChild method, with no problems:

public void addChild(final Tree<T> subTree) {
    getChildren().add(new Tree<T>(this, subTree));  // copy the tree & set parent attach to this
}

Then, we want to make the addChild method more general, which allows adding a tree of sub-type.

private class RedTree<T> extends Tree<T> {}
private void showArrayListIsOkForSubType() {
    RedTree<T> redTree = new RedTree();
    getChildren().add(redTree);
    getChildren().add(new RedTree());
}

In concept, we want to modify the addChild method to this:

(But the following code is having compile errors, shown in comments.)

public <Leaf extends T, SubTree extends Tree<T>> void add(final SubTree<Leaf> subTree) {
    // The type SubTree is not generic; it cannot be parameterized with arguments <Leaf>

    SubTree<Leaf> tr = new SubTree<Leaf>();
    getChildren().add(new SubTree<Leaf>());
    // SubTree cannot be resolved to a type
    // Leaf cannot be resolved to a type
}

We have searched through stackoverflow, but still with no helps. Could you please help us with the correct syntax?


My codes

With the guide and explanation from @Jason C, here is my codes. Hope it helps others :)

Also, please feel free to correct me :)

Note: the codes are not 100% complete. But all the major pieces are included.

First, in the default zero-argument constructor, ensure all the sub-classes define the copy-constructor.

/** Default constructor. **/
public Tree() {     // All sub-classes instantiation must invoke this default constructor
    super();                // here is a good place to ensure every sub-class has a copy constructor
    if (Reflection.hasCopyConstructor(this) == false)
        throw new CopyConstructorRequiredException(this.getClass());
}
class Reflection {
    public static boolean hasCopyConstructor(final Object object) {
        return hasCopyConstructor(object.getClass());
    }
    public static boolean hasCopyConstructor(final Class<?> clazz) {
        try {
            clazz.getDeclaredConstructor(clazz);
            return true;
        } catch (SecurityException e) {
            e.printStackTrace();
            return false;
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            return false;
        }
    }
}

Then this is the copy constructor of the base Tree<T>:

private Tree(final Tree<? extends T> copyFrom) {
    super();
    if (copyFrom != null) {
        this.setData(copyFrom.getData());
        for (final Tree<? extends T> child : copyFrom.getChildren()) {
            this.addChildren(child);    // addChildren() handles null well
        }
    }
}

Only the Generic Parameter <T> needs a wildcard of sub-class <? extends T>.

The parameter Tree inherently accepts all the sub-classes of Tree, by auto casting.

So, this copy constructor already able to accept Tree<T>, SubTree<T>, Tree<SubT>, and SubTree<SubT>.

For the copy constructor of the extended classes, it can be just simply:

private static class BlueTree<T> extends Tree<T> {
    private BlueTree(final BlueTree<T> blueTree) { super(blueTree); }
}

Back to the base class, Tree. Here is how addChild stores the objects.

public Tree<T> addChildren(final Tree<? extends T>... subTrees) {
    if (subTrees == null)               // called addChildren((Tree<T>) null)
        addChild((Tree<T>) null);           // add null to children
    else
        for (final Tree<? extends T> subTree : subTrees)      // empty parameter goes here != null array
            addChild(subTree);
    return this;
}
public Tree<T> addChild(final Tree<? extends T> subTree) {
    if (subTree == null)            // for addChild((Tree<T>) null)
        getChildren().add(null);        // add null to children
    else {                          // else
        getChildren().add(              // copy (constructor) the tree & set parent attach to this
                Reflection.<Tree<T>>invokeConstructor(subTree, new ParameterTypeAndArg(subTree.getClass(), subTree))
                .setParent(this));
    }
    return this;
}

Because we have checked every sub-class must contain the default constructor, we can safely invoke it here by reflection, to get a new instance of the sub-class, and store it into the children ArrayList.

P.S. we need to use reflection invoke because the ordinary new doesn't work for generic parameters.

midnite
  • 5,157
  • 7
  • 38
  • 52
  • Can you show us more code? What are the constructors, etc? – Zhe Aug 14 '13 at 02:37
  • `public void add(final Tree extends T> subTree)` would be sufficient. No need for parameterized `add()`. See answer below. – Jason C Aug 14 '13 at 02:50
  • the "compiler error" section looks like Scala =D In Scala we can write functions that take generic type parameters. –  Aug 14 '13 at 03:30

1 Answers1

1

Well, first of all, you're overcomplicating this. All you really need to do is:

public void add(final Tree<? extends T> subTree) {

Theres no need to parameterize add().

But anyways, I'll address your original attempt: You want SubTree extends Tree<Leaf>, because even if Leaf extends T you can't guarantee that SubTree extends Tree<T> with SubTree<Leaf> matches. E.g. if your class hierarchy is:

public class Base { }
public class A extends Base { }
public class B extends Base { }

If Leaf is A and SubTree is Tree<B> then add (final SubTree<Leaf>) does not match Tree<B>.

So conceptually you actually want this:

public <Leaf extends T, SubTree extends Tree<Leaf>> void add(final SubTree<Leaf> subTree) {

Of course that is not valid syntax. Really all you need to do is this:

public <Leaf extends T, SubTree extends Tree<Leaf>> void add(final SubTree subTree) {

That will be sufficient to match all necessary types. Try it:

{
    Tree<Object> x = new Tree<Object>();
    MyTree<Integer> y = new MyTree<Integer>();
    Tree<Integer> z = new Tree<Integer>();

    x.add(y);
    y.add(x); // not valid, as Tree<Object> does not extend Tree<Integer>  
    y.add(z); // fine, as Tree<Integer> matches
}

public static class MyTree<T> extends Tree<T> {     
}

Within add() you also do not parameterize SubTree, since its type is already specified elsewhere:

SubTree tr = ...;

However, now you have a bigger problem, which is a classic one and is answered in many other places here:

tr = new SubTree();

You cannot instantiate an object of a generic type because of type erasure. You will have to specify the SubTree's Class somewhere and instantiate it with .newInstance().

Jason C
  • 38,729
  • 14
  • 126
  • 182
  • Thanks! Really great answer, with great explanations! I have a few questions. If i use the ` extends T>` approach, can i still call the `SubTree`'s constructor, to really create a SubTree, but not casting it to a `Tree`? That is really a big problem that we cannot create an object for the generic type. Then `newInstance()` is the solution. Does it mean i have to copy the values from `subTree` to `anotherNewNode` afterwards? – midnite Aug 14 '13 at 03:00
  • You *cannot* instantiate an object from a generic type alone. Period. You *must* have the `Class` of the object available. Then you can use `Class.newInstance()` to instantiate an object of that type with the default constructor, or you can use `Class.getConstructor(...)` to get a specific non-default constructor. Whether or not you want to copy values or store references or whatever is entirely up to you and depends on your application requirements. If you need to copy them then you have to copy them. If you don't need to copy them, then you don't have to. Do what you need to do. :) – Jason C Aug 14 '13 at 03:07
  • Also you would never need to explicitly cast `SubTree` to a `Tree`. You can always store a `SubTree` in a variable that can hold a `Tree` without any explicit casts, since `SubTree` extends `Tree`. You *would* need to explicitly cast the reverse (and that cast may fail unless you know the `Tree` is a `SubTree`). Anyways, `Class.newInstance()`'s return type is `T`. See http://docs.oracle.com/javase/7/docs/api/java/lang/Class.html . – Jason C Aug 14 '13 at 03:09
  • 1
    Thanks so so so much!! Based on your answer and explanations, i have learnt a lot, and built a very generic Tree, which can accept and hold (not re-creating to the super-class) nodes of SubTree, Tree, SubTree. I used the default constructor to ensure all the sub-classes define a copy constructor. Such that, while insertion, i can safely use reflection to call the sub-class's copy constructor. The codes are posted above. Please feel free to correct me. And i hope it will help others as well :-) – midnite Aug 14 '13 at 10:47