Java:InstanceofおよびGenerics


135

値のインデックスのジェネリックデータ構造を調べる前に、それがthisパラメーター化されている型のインスタンスでさえあるかどうかを確認したいと思います。

しかし、これを行うとEclipseは不平を言います。

@Override
public int indexOf(Object arg0) {
    if (!(arg0 instanceof E)) {
        return -1;
    }

これはエラーメッセージです。

型パラメーターEに対してinstanceofチェックを実行できません。ジェネリック型情報は実行時に消去されるため、代わりにその消去オブジェクトを使用してください

それを行うためのより良い方法は何ですか?

回答:


79

エラーメッセージはそれをすべて言います。実行時にタイプがなくなったので、それをチェックする方法はありません。

次のようにオブジェクトのファクトリを作成することで、それをキャッチできます。

 public static <T> MyObject<T> createMyObject(Class<T> type) {
    return new MyObject<T>(type);
 }

次に、オブジェクトのコンストラクターにその型を格納し、変数を変更して、メソッドが次のようになるようにします。

        if (arg0 != null && !(this.type.isAssignableFrom(arg0.getClass()))
        {
            return -1;
        }

3
望んでいないと思うClass.isAssignableFrom
トムホーティン-

@トム、私は昨夜、記憶からこれを書いて、実際にクラスに合格するように修正しました(そうです!)さもなければ、なぜあなたはそれを望まないのか分かりません(たぶん今朝はもっとコーヒーが必要なのですが、私はmは最初のカップのみ)。
Yishai、

トムと一緒です。はっきりさせていただけますか?isAssignableFrom()を使用するのが私の仕事の選択肢でした。多分私は何かが足りないのですか?
luis.espinal 2010

6
@luis、トムのコメントはおそらくisInstance()を使用して実際のarg0パラメータを渡すことを意味していました。nullチェックを回避するという利点があります。
Yishai

1
似たような状況ですが、答えがよくわかりません。私のようなダミーの方が分かりやすく説明してもらえますか?
Rubens Mariuzzo

40

ジェネリックでのランタイム型チェックの2つのオプション:

オプション1-コンストラクターを破損する

indexOf(...)をオーバーライドしていて、パフォーマンスのためだけに型をチェックして、コレクション全体を繰り返し処理する手間を省こうとしているとします。

次のような不潔なコンストラクタを作成します。

public MyCollection<T>(Class<T> t) {

    this.t = t;
}

次に、isAssignableFromを使用してタイプを確認できます。

public int indexOf(Object o) {

    if (
        o != null &&

        !t.isAssignableFrom(o.getClass())

    ) return -1;

//...

オブジェクトをインスタンス化するたびに、自分自身を繰り返す必要があります。

new MyCollection<Apples>(Apples.class);

あなたはそれが価値がないと決めるかもしれません。ArrayList.indexOf(...)の実装では、型が一致することを確認しません。

オプション2-失敗させる

不明なタイプを必要とする抽象メソッドを使用する必要がある場合、本当に必要なのは、コンパイラーがinstanceofについて叫ぶのをやめることだけです。このようなメソッドがある場合:

protected abstract void abstractMethod(T element);

次のように使用できます。

public int indexOf(Object o) {

    try {

        abstractMethod((T) o);

    } catch (ClassCastException e) {

//...

コンパイラをだますために、オブジェクトをT(ジェネリック型)にキャストしています。キャストは実行時何も行いませんが、間違ったタイプのオブジェクトを抽象メソッドに渡そうとすると、ClassCastExceptionが発生します。

注1:抽象メソッドで追加のチェックされていないキャストを実行している場合、ClassCastExceptionsがここでキャッチされます。それは良いことも悪いこともあるので、よく考えてください。

注2:instanceofを使用すると、無料のnullチェックを取得します。使用できないため、素手でnullを確認する必要があるかもしれません。


18

古い投稿ですが、一般的なinstanceOfチェックを行う簡単な方法です。

public static <T> boolean isInstanceOf(Class<T> clazz, Class<T> targetClass) {
    return clazz.isInstance(targetClass);
}

21
ここで実際に貢献している内容は不明です。
Andrew

12

クラスがジェネリックパラメーターを使用してクラスを拡張する場合、実行時にリフレクションを介してこれを取得し、それを比較に使用することもできます。

class YourClass extends SomeOtherClass<String>
{

   private Class<?> clazz;

   public Class<?> getParameterizedClass()
   {
      if(clazz == null)
      {
         ParameterizedType pt = (ParameterizedType)this.getClass().getGenericSuperclass();
          clazz = (Class<?>)pt.getActualTypeArguments()[0];
       }
       return clazz;
    }
}

上記の場合、実行時にgetParameterizedClass()からString.classを取得し、それをキャッシュして、複数のチェックでリフレクションオーバーヘッドが発生しないようにします。他のパラメーター化された型は、ParameterizedType.getActualTypeArguments()メソッドからインデックスによって取得できることに注意してください。


7

私は同じ問題を抱えていましたが、これが私の解決策です(非常に謙虚、@ george:今回はコンパイルして動作しています...)。

私の問題は、Observerを実装する抽象クラスの中にありました。Observableは、任意の種類のオブジェクトである可能性のあるObjectクラスでメソッドupdate(...)を起動します。

タイプTのオブジェクトのみを処理したい

解決策は、実行時に型を比較できるように、クラスをコンストラクターに渡すことです。

public abstract class AbstractOne<T> implements Observer {

  private Class<T> tClass;
    public AbstractOne(Class<T> clazz) {
    tClass = clazz;
  }

  @Override
  public void update(Observable o, Object arg) {
    if (tClass.isInstance(arg)) {
      // Here I am, arg has the type T
      foo((T) arg);
    }
  }

  public abstract foo(T t);

}

実装では、コンストラクタにクラスを渡すだけです

public class OneImpl extends AbstractOne<Rule> {
  public OneImpl() {
    super(Rule.class);
  }

  @Override
  public void foo(Rule t){
  }
}

5

または、Eにキャストする試みの失敗をキャッチすることもできます

public int indexOf(Object arg0){
  try{
    E test=(E)arg0;
    return doStuff(test);
  }catch(ClassCastException e){
    return -1;
  }
}

1
+1簡単なチェックのための実用的な非過剰設計ソリューション!私はもっ​​とこれに賛成票を投じ
られれ

24
これは機能しません。Eは実行時に消去されるため、キャストは失敗せず、コンパイラに関する警告が表示されるだけです。
Yishai 2013年

1

厳密にはそうする必要はないはずです。それがジェネリックスのポイントなので、コンパイルタイプのチェックを行うことができます。

public int indexOf(E arg0) {
   ...
}

ただし、クラス階層がある場合は、@ Overrideが問題になることがあります。それ以外の場合はYishaiの回答を参照してください。


そう、Listインターフェイスは、関数がオブジェクトパラメータを取ることを要求します。
Nick Heiner、

あなたはリストを実装していますか?なぜList <E>を実装しないのですか?
Jason S、

(たとえば、ArrayListの宣言を参照してください:java.sun.com/j2se/1.5.0/docs/api/java/util/ArrayList.html
Jason S

@Rosarch:ListインターフェースのindexOf()メソッドでは、指定された引数が、探しているオブジェクトと同じ型である必要はありません。それは.equals()でなければならず、異なるタイプのオブジェクトは互いに.equals()にすることができます。これは、remove()メソッドの場合と同じ問題であり、以下を参照してくださいstackoverflow.com/questions/104799/...
newacct

1
[[[急いで]]]気にしないで、indexOf()がObjectではなくEをパラメーターとして必要とすると思いました。(なぜ彼らはそれをしたのですか?!??!)
Jason S

1

オブジェクトの実行時タイプは、フィルタリングするのが比較的恣意的な条件です。コレクションからそのような汚さを遠ざけることをお勧めします。これは、コレクションに渡されたフィルターにコレクションを委任することで簡単に実現できます。

public interface FilterObject {
     boolean isAllowed(Object obj);
}

public class FilterOptimizedList<E> implements List<E> {
     private final FilterObject filter;
     ...
     public FilterOptimizedList(FilterObject filter) {
         if (filter == null) {
             throw NullPointerException();
         }
         this.filter = filter;
     }
     ...
     public int indexOf(Object obj) {
         if (!filter.isAllows(obj)) {
              return -1;
         }
         ...
     }
     ...
}

     final List<String> longStrs = new FilterOptimizedList<String>(
         new FilterObject() { public boolean isAllowed(Object obj) {
             if (obj == null) {
                 return true;
             } else if (obj instanceof String) {
                 String str = (String)str;
                 return str.length() > = 4;
             } else {
                 return false;
             }
         }}
     );

(あなたが使用している場合は、インスタンスの型チェックを行いたいかもしれませんがComparatorまたは同様。)
トムホーティン- tackline
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.