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
Tree<T>
(of course),SubTree<T>
whereSubTree extends Tree
,Tree<SubT>
whereSubT extends T
, andSubTree<SubT>
whereSubTree extends Tree
andSubT 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:
- holding "all" the sub-classes, and
- 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.