0

目标

例如,我们有一个Treetype 类T: ie Tree<T>

我们想让这个Tree<T>班级能够举行

  1. Tree<T>(当然),
  2. SubTree<T>其中SubTree extends Tree,
  3. Tree<SubT>哪里SubT extends T, 和
  4. 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)类时,这变成了一个大问题。

所以,我们的目标是:

  1. 持有“所有”子类,以及
  2. 结构中的每个元素都是独一无二的。

(下面的解决方案)


原来的问题

我们有一个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;
}

因为我们已经检查了每个子类都必须包含默认构造函数,所以我们可以在这里通过反射安全地调用它,以获取子类的新实例,并将其存储到childrenArrayList 中。

PS我们需要使用反射调用,因为普通new参数不适用于泛型参数。

4

1 回答 1

1

嗯,首先,你把这件事复杂化了。您真正需要做的就是:

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 { }

if LeafisASubTreeis Tree<B>thenadd (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().

于 2013-08-14T02:40:44.460 回答