目标

例如,我们有一个Tree类型为T的类:即Tree<T>

我们希望使此Tree<T>类能够容纳


Tree<T>(当然),
SubTree<T>其中SubTree extends Tree
Tree<SubT>其中SubT extends T
SubTree<SubT>其中SubTree extends TreeSubT 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 { }


如果LeafA并且SubTreeTree<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()实例化它。

10-06 13:51
查看更多