標的
たとえば、次のようなタイプのクラスTree
があります。T
Tree<T>
Tree<T>
このクラスを開催できるようにしたいと思います
Tree<T>
(もちろん)、SubTree<T>
ここでSubTree extends Tree
、Tree<SubT>
、SubT extends T
およびSubTree<SubT>
どこSubTree extends Tree
と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
メソッドを次のように変更します。
(ただし、次のコードにはコンパイル エラーがあり、コメントに示されています。)
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;
}
すべてのサブクラスにデフォルトのコンストラクターが含まれている必要があることを確認したので、ここでリフレクションによって安全に呼び出して、サブクラスの新しいインスタンスを取得し、それをchildren
ArrayList に格納できます。
new
PS通常はジェネリックパラメーターでは機能しないため、リフレクション呼び出しを使用する必要があります。