例如,我们有一个类型为Tree
的{{1}}类:T
。
我们希望这个Tree<T>
课能够举行
Tree<T>
(当然),Tree<T>
其中SubTree<T>
,SubTree extends Tree
其中Tree<SubT>
和SubT extends T
其中SubTree<SubT>
和SubTree extends Tree
。“Hold”表示它接受某个子类,和它会根据请求分别返回某个子类的对象。
例如,原始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
我们已经搜索了stackoverflow,但仍然没有任何帮助。你能用正确的语法帮助我们吗?
根据@Jason C的指南和解释,这是我的代码。希望它能帮助别人:)
另外,请随时纠正我:)
注意:代码不是100%完整。但所有主要部分都包括在内。
首先,在默认的零参数构造函数中,确保所有子类都定义了复制构造函数。
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
}
然后这是基础/** 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;
}
ArrayList中。 / p>
P.S。我们需要使用反射调用,因为普通的children
不适用于泛型参数。
答案 0 :(得分:1)
嗯,首先,你过于复杂了。你真正需要做的就是:
public void add(final Tree<? extends T> subTree) {
Theres无需参数化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();
由于类型擦除,您无法实例化泛型类型的对象。您必须在某处指定SubTree的Class
并使用.newInstance()
对其进行实例化。