目标
例如,我们有一个Tree
type 类T
: ie Tree<T>
。
我们想让这个Tree<T>
班级能够举行
Tree<T>
(当然),SubTree<T>
其中SubTree extends Tree
,Tree<SubT>
哪里SubT extends T
, 和SubTree<SubT>
哪里SubTree extends Tree
和SubT extends T
。
“保持”表示接受某个子类,根据请求分别返回某个子类的对象。
例如,原来的ArrayList
有这个属性:
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
}
这是因为原始ArrayList
只是保留输入对象的引用,而不是重新创建它。这不是构建我的类时所期望的行为,尤其是一棵树。考虑以下示例:
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
}
为什么这是一场灾难?考虑树中的某个节点,我们想要找到下一个兄弟节点。
事实上,我们可以在插入元素时做一个快速修复:
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 :-)
}
但是,当涉及到构建我们自己的(的Tree
)类时,这变成了一个大问题。
所以,我们的目标是:
- 持有“所有”子类,以及
- 结构中的每个元素都是独一无二的。
(下面的解决方案)
原来的问题
我们有一个Tree
类,用于ArrayList
保存节点:
public class Tree<T> {
// some constructors & methods skipped
private final ArrayList<Tree<T>> mChildren = new ArrayList<Tree<T>>();
}
我们有这个addChild
方法,没有问题:
public void addChild(final Tree<T> subTree) {
getChildren().add(new Tree<T>(this, subTree)); // copy the tree & set parent attach to this
}
然后,我们想让该addChild
方法更通用,允许添加子类型的树。
private class RedTree<T> extends Tree<T> {}
private void showArrayListIsOkForSubType() {
RedTree<T> redTree = new RedTree();
getChildren().add(redTree);
getChildren().add(new RedTree());
}
在概念上,我们希望将addChild
方法修改为:
(但以下代码存在编译错误,如注释中所示。)
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
}
我们已经通过 stackoverflow 进行了搜索,但仍然没有任何帮助。你能帮助我们正确的语法吗?
我的代码
借助@Jason C 的指南和解释,这是我的代码。希望它可以帮助别人:)
另外,请随时纠正我:)
注意:代码并非 100% 完整。但所有主要部分都包括在内。
首先,在默认的零参数构造函数中,确保所有子类都定义了复制构造函数。
/** 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;
}
}
}
然后这是 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
}
}
}
只有通用参数<T>
需要 sub-class 的通配符<? extends T>
。
该参数通过自动转换Tree
固有地接受 的所有子类。Tree
所以,这个复制构造函数已经能够接受Tree<T>
, SubTree<T>
,Tree<SubT>
和SubTree<SubT>
.
对于扩展类的拷贝构造函数,可以很简单:
private static class BlueTree<T> extends Tree<T> {
private BlueTree(final BlueTree<T> blueTree) { super(blueTree); }
}
回到基类,Tree
. 这是addChild
存储对象的方式。
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;
}
因为我们已经检查了每个子类都必须包含默认构造函数,所以我们可以在这里通过反射安全地调用它,以获取子类的新实例,并将其存储到children
ArrayList 中。
PS我们需要使用反射调用,因为普通new
参数不适用于泛型参数。