4

私は自分のプロジェクトの1つに一般性を追加することになっています。ジェネリックスが大好きです。これにより、コードがより堅牢になり、自己文書化され、醜いキャストがすべて消去されます。

しかし、私は困難なケースに遭遇し、私の構造の1つに「再帰的」制約を表現しようとしていくつかの問題が発生しました。

これは基本的に、(子と親への)二重リンクを持つある種の「一般的な」ツリーです。問題を示すために、クラスを最大で簡略化しました。

public class GenericTree<
    ParentClass extends GenericTree<?, ?>, 
    ChildClass extends GenericTree<?, ?>> 
{
    // Attributes
    private ArrayList<ChildClass> children = new ArrayList<ChildClass>();
    private ParentClass parent = null;

    // Methods 
    public void setParent(ParentClass parent) {
        this.parent = parent;
    }

    public void addChild(ChildClass child) {
        child.setParent(this);
        this.children.add(child);
    }
}

問題は、命令:child.setParent(this)にあります。

Javaは次のエラーを出します:

バインドの不一致:タイプChildClassのメソッドsetParent(?)は、
引数(GenericTree)には適用できません。ワイルドカードパラメータ?下限はなく、実際には引数GenericTreeよりも制限が厳しい可能性があります

私が欲しいのは、次のようなものを表現できるようにすることです。

public class GenericTree<
    ParentClass extends GenericTree<?, ?>, 
    ChildClass extends GenericTree<[THIS_VERY_CLASS], ?>> 

子クラスの親クラスはそれ自体である必要があると言うには...

自己境界ジェネリックに関するいくつかの記事を見てきましたが、この場合の適用方法がわかりません。

どんな助けでもいただければ幸いです。

4

2 に答える 2

2

不均一なツリーにジェネリックを使用しないでください。インターフェイスとキャストを使用してください。

ジェネリックスの問題のいくつかを解決することはできますが、結果のコードはもろくなり、これまでに見たことのないようなエラーメッセージが表示されます。バグを修正すると、通常はtry&errorが発生し、コンパイルしても失敗します。理由を知っている ;-)

[EDIT2]addChild()以下に使用例を追加しました。

[編集]まだ私と一緒ですか?本当に必要な場合は、次のAPIを使用してください。

interface ParentNode<Child> {
    List<Child> getChildren();
    void addChild(Child child);
}
interface ChildNode<Parent> {
    void setParent(Parent parent);
    Parent getParent();
}
// There is no way to avoid this because we would need to define
// "Node" recursively.
@SuppressWarnings( "rawtypes" )
class Node<
    Parent extends ParentNode<? extends Node>,
    Child extends ChildNode<? extends Node>
>
implements
    ParentNode<Child>,
    ChildNode<Parent>
{
    private Parent parent;
    public Parent getParent() { return parent; }
    public void setParent(Parent parent) { 
        this.parent = parent;
        // Here, we must case the child to a type that will accept Node
        @SuppressWarnings( "unchecked" )
        ParentNode<Node> cast = (ParentNode)parent;
        cast.addChild(this); // Note: Either add the child here ...
    }

    private List<Child> children;
    public List<Child> getChildren() { return children; }
    public void addChild( Child child ) { 
        children.add(child);
        // Here, we must case the child to a type that will accept Node
        @SuppressWarnings( "unchecked" )
        ChildNode<Node> cast = (ChildNode)child;
        cast.setParent(this); // ... or here but not twice :-)
    }
}

つまり、2つの関数(上向きと下向き)を2つのインターフェイスに分割し、両方を実装するノードタイプを作成します。これにより、リーフノードとルートノードを(2つのAPIのいずれかを使用せずに)特別なノードとして定義したり、他のノードと同じように定義しnullて「サポートされていない」メソッドで返すことができます。

使用法:

public class DirFileNode extends Node<DirFileNode, DirFileNode> {
}
public class TreeUsage {
    public static void main( String[] args ) {
        DirFileNode node = new DirFileNode();
        DirFileNode node2 = new DirFileNode();
        node.addChild( node2 );
        // Why yes, I do love infinite loops. How can you tell?
        node2.addChild( node );
    }
}

ご覧のとおり、APIはすべてがタイプセーフであることを確認しますが、内部的にはキャストする必要があります。そして、これは、ノードでジェネリック型を使用しない限り、単純です。あなたがそうするならば、巨大な混乱に成長する宣言。

于 2010-07-26T09:55:18.543 に答える
1

私が得ることができる最も近いものは、列挙型の自己参照のパターンに従うことです。それでもチェックされていないキャストが必要ですが、サブクラスがTを正しく定義していると仮定すると、安全であるはずです。

public class GenericTree<T extends GenericTree<T, P, C>, P extends GenericTree<P, ?, T>, C extends GenericTree<C, T, ?>> {

    // Attributes
    private ArrayList<C> children = new ArrayList<C>();
    private P parent = null;

    // Methods
    public void setParent(P parent) {
        this.parent = parent;
    }

    public void addChild(C child) {
        @SuppressWarnings("unchecked")
        final T thisAsType = (T) this;
        child.setParent(thisAsType);
        this.children.add(child);
    }
}

編集:実装例

public static class SingleTypeTree<T> extends
    GenericTree<SingleTypeTree<T>, SingleTypeTree<T>, SingleTypeTree<T>> {

}
于 2010-07-26T14:27:30.327 に答える