2011-07-09 21 views
3

Javaコレクションのインターフェイス(たとえば、ListまたはSet)は、任意のオブジェクトを受け入れる方法を定義しています(contains)。ジェネリック型のチェック

public boolean contains(Object o) 

それは、このメソッドを実装するに来るときしかし、私が働いている特定のコレクションは、私はクラスのジェネリック型Eと互換性のあるタイプ(すなわちを持っているいずれかのクラスのEである必要があり、またはEのサブクラス、またはEがインタフェースの場合はEを実装するクラス)。言い換えれば、oがE型にキャスト可能であれば、互換性があります。

public boolean contains(Object o) 
{ 
    if(o instanceof E) // compile error due to type erasures 
    { 
     // ... check if this collection contains o 
    } 
    return false; 
} 

私の質問は、このような何かを達成するための最良の方法だろうものです:Javaは一般的なタイプの情報を消去するので、このような何かが不可能であるため、これは問題を提示しますか? Oracle Article on Type Erasuresにはこれが禁止されていると記載されていますが、この問題の解決策はありません。

私はこれを回避する一つの半エレガントな方法を考えることができます。キャストが失敗した場合、キャストはE.を入力してください

、oはタイプEやタイプのサブクラスすることはできませんE.

public boolean contains(Object o) 
{ 
    try 
    { 
     E key = (E) o; // I know it's unsafe, but if o is castable to E then the method should work 
     // check if this collection contains key 
    } 
    catch(ClassCastException e) 
    { 
     // invalid type, cannot contain o 
    } 
    return false; 
} 

これはうまくいくものの、見た目が乱雑です(このように例外を使用するのは大したファンではありません)。

この同じ目標(メソッドシグネチャの変更は許可されていません)を実行するより良い方法はありますか?

編集:Eがオブジェクトに消去されますので、ええ、これは動作しません:(

+3

動作しますか?チェックされていないキャストコンパイラ警告が表示されます。 ClassCastExceptionを受け取ることはありません( 'o'は' E'の** erasure **のインスタンスではない(おそらく 'Object'))。 –

+0

"あなたはクラスのジェネリックタイプEと互換性のある型を必要としますか?"ありがとう。 –

+0

私は同意する - これは動作しません。実際のクラスが必要で、失敗する可能性のある適切なキャストを行う必要があります。 – StaxMan

答えて

4

これは予想Classに(1)通過によってのみ可能である、または(2)いくつかのジェネリック型パラメータを調べます<E>を定義する反射素子私は詳しく説明しましょう

最も一般的なケースはEのランタイムクラスに渡すために、発信者を単に要求することです

public class MyClass<E> { 
    private final Class<E> realType; 
    public MyClass(Class<E> realType) { 
     this.realType = realType; 
    } 
    public boolean Contains(Object o) { 
     E e = realType.cast(o); // runtime cast - will throw ClassCastException. 
     // Could also use realType.isInstance(o) 
     // or realType.isAssignableFrom(o.getClass()) 
     ... 
    } 
} 

発信者:。。。

new MyClass<MyObject>(MyObject.class) 

コンパイラは<E>が一致することを確認するので、これは一般的にタイプセーフです。もちろん、呼び出し元はコンパイラのチェックをバイパスすることができます...あなたはそれについて何もできません!

(2)については、反射を使用してスタティックジェネリック型パラメータを調べることができます。 <E>を静的に定義するフィールド、メソッド、またはスーパークラスの宣言にアクセスする必要があるため、これはおそらく適切な選択肢ではありません。最も一般的な方法は、クラスを抽象クラスにして呼び出し元にそれを拡張させることです。このアプローチは、HamclrestのTypeSafeMatcherReflectiveTypeFinderを参照)によって大きな効果を発揮します。 (TypeSafeMatcherは、基本的にはプログラマにとって簡単にoption(1)を作成するだけで、リフレクションがうまくいかない場合にクラスを取得するコンストラクタを提供しています!)本当に夢中になりたい場合は、getClass().getGenericSuperclass().getActualTypeArguments()を調べることができます。これは聞こえるほど簡単ではありません - this good articleを参照してください。私は記事がすべての端のケースをカバーしていることさえ確信していません - あなたは基本的にコンパイラを再実装しています!だから、オプション(1)を使い、C#でジェネリックスを使用していないことをうれしく思います。

+0

手作業でこのようなタイプを設定している場合、何がジェネリックのポイントですか? –

+0

ジェネリックスは、ほとんどのキャストを排除し、コンパイラのタイプチェックを改善することによって、ソースコードをクリーンアップします。タイプ消去を使用するJavaの決定を守るのではなく、.NETのジェネリックも欠点があります。 –

0

私はこのように.contains()を使いこなしていません。 の指定がCollectionの場合、指定されたオブジェクトが(.equals()の意味で)コンテナのいくつかの要素と等しい場合はtrueを返します。あなたは.contains()の意味を変えていますか?あなたの平等はどのように定義されていますか?

+0

Java TreeSetクラスはequals()メソッドを使用して等価を定義しません(代わりにcompareTo()メソッドまたはComparatorを使用します)。そのTreeSetのジェネリックパラメータにキャストできないオブジェクトを渡そうとすると、ClassCastException(contains()メソッドのJavadocの一部です)です。私は、木のようなデータ構造の異なるタイプの同様の状況があります。面白いことに、Javadocはcontains()メソッドに対してequals()メソッドを使用していると言います。 – helloworld922

+0

@ helloworld922: "そのTreeSetのジェネリックパラメータにキャストできないオブジェクトを渡そうとするのは、ClassCastExceptionです。"そうではありません。実行時にジェネリックパラメータのようなものはありません。 TreeSetは、実行時にオブジェクトを使って、Comparableにキャストし、compareToを使用して他のオブジェクトと比較します。 ClassCastExceptionは、入れているオブジェクトが互いに比較できない場合にのみ発生します。 – newacct

+0

ああ、本当です。私は、Comparableオブジェクトを通常はそれに匹敵するものに使用していますが、そうである必要はありません。 – helloworld922