目标
例如,我们有一个Tree
类型为T
的类:即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;
}
}
}
然后,这是基本
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>
需要子类<? 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中。附言我们需要使用反射调用,因为普通的
new
不适用于泛型参数。 最佳答案
好吧,首先,您使此过程变得过于复杂。您真正需要做的就是:
public void add(final Tree<? extends T> subTree) {
无需参数化
add()
。但是无论如何,我将解决您最初的尝试:您想要
SubTree extends Tree<Leaf>
,因为即使Leaf extends T
您也不能保证SubTree extends Tree<T>
与SubTree<Leaf>
匹配。例如。如果您的类层次结构是:public class Base { }
public class A extends Base { }
public class B extends Base { }
如果
Leaf
是A
并且SubTree
是Tree<B>
,则add (final SubTree<Leaf>)
与Tree<B>
不匹配。因此,从概念上讲,您实际上想要这样:
public <Leaf extends T, SubTree extends Tree<Leaf>> void add(final SubTree<Leaf> subTree) {
当然那是无效的语法。真的,您需要做的就是:
public <Leaf extends T, SubTree extends Tree<Leaf>> void add(final SubTree subTree) {
这将足以匹配所有必需的类型。尝试一下:
{
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> {
}
在
add()
中,您也无需参数化SubTree
,因为它的类型已经在其他地方指定:SubTree tr = ...;
但是,现在您遇到了一个更大的问题,这是一个经典的问题,可以在以下许多其他地方得到解答:
tr = new SubTree();
由于类型擦除,您无法实例化通用类型的对象。您将必须在某处指定子树的
Class
并使用.newInstance()
实例化它。