2016-05-06 6 views
6

設計インターフェース

interface HierarchicalEntity<T extends HierarchicalEntity<T>> { 
    T getParent(); 
    Stream<T> getAncestors(); 
} 

それは前者がのStreamを返すようにgetParent()の面でデフォルトgetAncestors()メソッドを実装するのは非常に簡単ですすべての祖先。

実装例:

default Stream<T> getAncestors() { 
    Stream.Builder<T> parentsBuilder = Stream.builder(); 
    T parent = getParent(); 
    while (parent != null) { 
     parentsBuilder.add(parent); 
     parent = parent.getParent(); 
    } 
    return parentsBuilder.build(); 
} 

しかし、私はまた、ストリームにthisを含める必要があり、ここで問題が表示されます。 thisはタイプHierarchicalEntity、ないTであるため 次の行が正しくありません:

parentsBuilder.add(this); // type mismatch! 

どのように私はgetAncestors()が結果にthisを含めるようにするためにインターフェースを再設計することができますか?

+5

残念ながら、これは完全な型の安全ではありません。 Javaには自己参照型構文がありません。 'Fake implements HierarchicalEntity 'を作成すると 'getAncestors'はおそらく最終的に' ClassCastException'で失敗します。 –

+0

ポイントを得ました。多少の再設計がインタフェース全体に適用される可能性があります。 – Aliaxander

+0

'T'に' this'をキャストしてストリームビルダーに追加してください –

答えて

1

自己参照型を作成するときに繰り返し発生する問題です。ベースタイプ(またはインターフェイス)では、thisTと割り当て互換性があることを強制することはできません。

もちろん、すべてのサブタイプがその制約を満たすと確信している場合は、thisTのチェックされていないキャストを実行できます。しかし、thisの参照がTと必要なときには、このチェックされていないキャストを実行する必要があります。

よりよい解決策は、あなたがTとして自己参照を必要とするときにいつでも​​代わりのthisを使用することができ、その後

/** 
    All subtypes should implement this as: 

    public T myself() { 
     return this; 
    } 
*/ 
public abstract T myself(); 

のような抽象メソッドを追加することです。もちろん

default Stream<T> getAncestors() { 
    Stream.Builder<T> parentsBuilder = Stream.builder(); 
    for(T node = myself(); node != null; node = node.getParent()) { 
     parentsBuilder.add(parent); 
    } 
    return parentsBuilder.build(); 
} 

、あなたはサブクラスが正しくreturn this;として​​を実装することを強制することはできませんが、少なくとも、あなたは簡単に実行時に彼らが行うかどうかを確認することができます

assert this == myself(); 

このリファレンスの比較が非常にあります​​が正しくthisを返すように正しく実装されている場合、HotSpotはこの比較が常にtrueであることを事前に証明し、チェックを完全に削除します。

欠点は、それぞれの特殊化はmyself() { return this; }のこの冗長実装を持つ必要がありますが、一方ではチェックされていない型キャストは完全に排除されていることです。代わりに、基底クラスの以外の操作をタイプ階層の1つの場所に限定する場合は、の​​の宣言を@SuppressWarnings("unchecked") T myself() { return (T)this; }とします。しかし、thisが実際にタイプTであるかどうかを確認することはできません...

1

thisを追加すると、HierarchicalEntity<T>は必ずしもTではないため失敗します。それは未知のサブタイプである可能性があります。ただし、Tは、そのように宣言したとおり、常にHierarchicalEntity<T>です。

変更getAncestorsの、あなたがthisを追加することができますTHierarchicalEntity<T>からStream.Builder、の戻り値の型。

default Stream<HierarchicalEntity<T>> getAncestors() { 
    Stream.Builder<HierarchicalEntity<T>> parentsBuilder = Stream.builder(); 
    T parent = getParent(); 
    while (parent != null) { 
     parentsBuilder.add(parent); 
     parent = parent.getParent(); 
    } 
    parentsBuilder.add(this); 
    return parentsBuilder.build(); 
} 

あなたは、一貫性を保つためにもHierarchicalEntity<T>を返すようにgetParentを宣言することもできます。

+0

しかしOPは 'T'のストリームを望んでいます。 – shmosel

+0

@shmoselこれは現在のインターフェイス設計ですが、OPによってそのような明示的なステートメントはありません。 – rgettman

+0

@rgettman 'T'の実際の型は' HierarchicalEntity'で定義されていないいくつかのメソッドを持つことになっていますので、 'getAncestors()。map(ActualType :: foo)'のようなコードは無効になります。 – Aliaxander

2

@SotiriosDelimanolisが言っているように、これを完全に強制する方法はありません。しかし、あなたが設計通りにインタフェースが使用されていると仮定して喜んでいる場合、あなたはthisTのインスタンスであると仮定し、単にそれをキャストすることができます

parentsBuilder.add((T)this); 

あなたはキャストを避けたい場合は、メソッドを追加することができますサブクラスでオーバーライドする:

interface HierarchicalEntity<T extends HierarchicalEntity<T>> { 
    T getParent(); 
    T getThis(); 
    default Stream<T> getAncestors() { 
     // ... 
     parentsBuilder.add(getThis()); 
     // ... 
    } 
} 

class Foo extends HierarchicalEntity<Foo> { 
    // ... 
    @Override 
    public Foo getThis() { 
     return this; 
    } 
} 

今、私たちは、タイプセーフな方法でthisを得ることができますが、getThis()が正しく実装されているという保証はありません。 Fooのインスタンスを返すことは可能です。だから、私が推測するあなたの毒を選んでください。

+0

'getThis()'のやり方は適切だとはいえ、まだまだ厄介です... – Aliaxander

+0

@Aliaxander共通の抽象クラスを使用するオプションであれば、 'AbstractEntity(T thisObj)'コンストラクタを作成し、 'super (this) 'を呼び出すので、各サブクラスで' getThis() 'を実装する必要はありません。まだエレガントではありませんが、エレガントなソリューションがあるかどうかはわかりません。 – shmosel

+1

明示的なコンストラクタ呼び出しで 'this'を使用することはできません。http://docs.oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.8.7.1を参照してください。 – Alex

関連する問題