通用类型x通用参数:构建“非常通用”结构

时间:2013-08-14 02:25:45

标签: java generics methods arraylist generic-programming

AIM

例如,我们有一个类型为Tree的{​​{1}}类:T

我们希望这个Tree<T>课能够举行

  1. Tree<T>(当然),
  2. Tree<T>其中SubTree<T>
  3. SubTree extends Tree其中Tree<SubT>
  4. SubT extends T其中SubTree<SubT>SubTree extends Tree
  5. “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 :-) } )时,这就成了一个大问题。

    因此,我们的目标是:

    1. 持有“所有”子类,
    2. 让结构中的每个元素都是唯一的。
    3. (以下解决方案)


      原始问题

      我们有一个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的所有子类。

      因此,此副本构造函数已经能够接受TreeTree<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不适用于泛型参数。

1 个答案:

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

如果LeafASubTreeTree<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()对其进行实例化。