0

標的

たとえば、次のようなタイプのクラスTreeがあります。TTree<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>サブクラスのワイルドカードが必要なのはジェネリック パラメータのみ<? 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 に格納できます。

newPS通常はジェネリックパラメーターでは機能しないため、リフレクション呼び出しを使用する必要があります。

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

もし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()は parameterize も行いません。SubTree

SubTree tr = ...;

ただし、これは古典的な問題であり、ここの他の多くの場所で回答されています。

tr = new SubTree();

型消去のため、ジェネリック型のオブジェクトをインスタンス化することはできません。サブツリーのClassどこかを指定し、 でインスタンス化する必要があります.newInstance()

于 2013-08-14T02:40:44.460 に答える