2011-06-29 10 views
15

は、これら2つの宣言間の意味の違いはありますか、それが唯一のシンタックスシュガーで拡張する拡張? class C<T extends C<T>>ジェネリック:TはT対MyClassのは、MyClassの<T>

背景対

class C<T extends C>私は最近C<T extends C>アプローチを使用してジェネリックにquestionに答えてピアはC<T extends C<T>>に基づいて同様の答えを提供します。最終的に、両方の選択肢が同じ結果をもたらしました(質問された質問の文脈で)。私はこれらの2つの構成要素の違いについて興味があった。

意味に違いはありますか?もしそうなら、それぞれのアプローチの意味と結果は何か?

+2

私はあなたが求めているのではないことを知っていますが、最初のスニペットには生の型が含まれています。つまり、「適切な」ジェネリックコードではありません。下位互換性のためではないということは、これがハードコンパイラエラーであると私の意見です。 –

+0

私はこれがあなたの質問ではないことも知っていますが、プログラムはどちらの場合も同じように動作するので**意味論的な違いはありません。 –

答えて

9

の有界パラメータ<T extends D<T>>の代用として有効ではありません確かに - これらの「自己タイプ」はしばしば、サブタイプが自分のタイプを正確に返すように制約するために使用されます。次のようなものを考えてみましょう:

public interface Operation { 
    // This bit isn't very relevant 
    int operate(int a, int b); 
} 

public abstract class AbstractOperation<T extends AbstractOperation<T>> { 
    // Lets assume we might need to copy operations for some reason 
    public T copy() { 
     // Some clever logic that you don't want to copy and paste everywhere 
    } 
} 

クール - 私たちは、サブクラスに特異的であることができる便利な演算子を持つ親クラスを持っています。たとえば、AddOperationを作成した場合、一般的なパラメータは何ですか?そのため、「再帰的」の一般的な定義で、これが唯一のAddOperationたちを与えることができます。

public class AddOperation extends AbstractOperation<AddOperation> { 
    // Methods etc. 
} 

ので、copy()方法はAddOperationを返すことが保証されます。

public class SubtractOperation extends AbstractOperation<AddOperation> { 
    // Methods etc. 

    // Because of the generic parameters, copy() will return an AddOperation 
} 

ジェネリック型は、その境界内にないので、これは、コンパイラによって拒否されます。今、私たちは愚かな、または悪意のある、または創造的、または何だと想像し、このクラスを定義するために試すことができます。これは非常に重要です。つまり、親クラスでは、具体的な型がわからなくても(コンパイル時に存在しないクラスであっても)、copy()メソッドはインスタンスを返しますその同じサブクラスの

あなたは、単にC<T extends C>と一緒に行った場合、SubtractOperationのこの奇妙な定義は法的だろう、とあなたはTが、その場合には何であるかについての保証を失う - それゆえ減算演算は、追加操作に自身をコピーすることができます。

これは悪意のあるサブクラスからクラス階層を保護することではなく、コンパイラが関連する型についてより強力な保証を提供するということです。 copyを別のクラスのaltogtherから任意のOperationにコールしている場合は、いずれかのフォーメーションで結果が同じクラスであることが保証され、もう一方はキャストが必要です(は正しくキャストできません上記のSubtractOperation)。例えば、このような

何か:

// This prelude is just to show that you don't even need to know the specific 
// subclass for the type-safety argument to be relevant 
Set<? extends AbstractOperation> operations = ...; 
for (AbstractOperation<?> op : operations) { 
    duplicate(op); 
} 

private <T extends AbstractOperation<T>> Collection<T> duplicate(T operation) { 
    T opCopy = operation.copy(); 
    Collection<T> coll = new HashSet<T>(); 
    coll.add(operation); 
    coll.add(opCopy); 

    // Yeah OK, it's ignored after this, but the point was about type-safety! :) 
    return coll; 
} 

Tからduplicateの最初の行に割り当てがタイプセーフあなたが提案した2つの境界の弱いとならないので、コードはないだろうコンパイル。 の場合でも、すべてのサブクラスを分かりやすく定義します。

+0

+1詳細な回答ありがとうございます。 – maasg

+0

あなたはあなたのコード内の他の場所で 'Operation'を参照しません - そこに' Implementing'を実装したかったのですか? – Eric

4

のは、あなたが

class Animal<T extends Animal> 

Tが犬の場合は動物

と呼ばれるクラスがあるとしましょう、それは犬が動物<犬のサブクラス>、動物<猫>、動物<ドンキー>何もする必要があることを意味します。この場合

class Animal<T extends Animal<T>> 

Tが犬であれば、それは動物<犬のサブクラスである必要があります>と他には何もありません、あなたは

を持つことができないクラス

class Dog extends Animal<Dog> 

を持っている必要があります。すなわち

class Dog extends Animal<Cat> 

そして、あなたは、あなたが動物の型パラメータとして犬を使用することはできませんし、あなたが

を持たなければならないことがあっても、

多くの場合、特にいくつかの制約が適用されている場合は、2つの間に大きな違いがあります。

+0

はい、 'cat Cat extends Animal 'であれば、両方のケースで 'Dog Dog extends Animal 'を持つことができます。 – Marcelo

+0

猫が伸びている限りあなたは – aps

+0

を意味します。犬が伸びることを意味します。は、動物が伸びる限り動物です。一般的なテンプレートは、TがAnimal を拡張し、Cat **がAnimal を拡張できることを示しています。 – Marcelo

0

しかし、意味的な違いがあります。ここでは、最小限のイラストです:

class C<T extends C> { 
} 

class D<T extends D<T>> { 
} 

class Test { 
    public static void main(String[] args) { 
     new C<C>(); // compiles 
     new D<D>(); // doesn't compile. 
    } 
} 

誤差はかなり明白です:

バウンド不一致:タイプDはタイプD<T>

+1

すばらしい観察。 – djangofan

関連する問題