web-dev-qa-db-ja.com

ジェネリック型がJavaで特定のタイプのジェネリックインターフェイスを実装しているかどうかを確認する方法

可能性のある複製:
実行時のローカル変数の一般型

私はJava genericsが初めてで、.NETの世界から来て、このようなメソッドを書くことができることに慣れています:

_public void genericMethod<T>(T genericObject)
{
    if (genericObject is IList<String>)
    {
        //Do something...
    }            
}
_

このメソッドは、ジェネリック型のオブジェクトを受け入れ、そのオブジェクトがジェネリックインターフェイス_IList<>_(この場合は_IList<String>_)のspecificバージョンを実装しているかどうかをチェックします。

これで、Javaで次のことができるようになりました。

_public <T> void genericMethod(T genericObject)
{
    if (genericObject instanceof Set<?>)
    {
       //Do something...
    }
}
_

[〜#〜] but [〜#〜]

Javaはnotさせてくれますif (genericObject instanceof Set<String>)

私が知っていることから、型の消去のため、通常Javaでこれはクラスオブジェクトによって処理され、次のようなことをします。

_public <T> void genericMethod(T genericObject)
{
    Class<OurTestingType> testClass = OurTestingType.class;
    if (genericObject.getClass() == testClass)
    {
       //Do something...
    }
}
_

しかし、私がチェックしている型は汎用インターフェースなので、これを行うことはできません:

_Class<Set<String>> testClass = Set<String>.class_

それでは、Javaでは、ジェネリックオブジェクトが特定のタイプの_Set<String>_を実装しているかどうかをどのように確認しますか?

20
Eric Cornelson

Javaは消去を実装しているため、genericObjectが_Set<String>_のインスタンスであるかどうかを実行時に通知する方法はありません。これを保証する唯一の方法は、ジェネリックの境界を使用するか、セット内のすべての要素をチェックすることです。

コンパイル時の一般的な境界

コンパイル時にチェックされる境界チェックの使用:

_public <T extends SomeInterface> void genericMethod(Set<? extends T> tSet) {
    // Do something with tSet here
}
_

Java 8

Java 8のストリームを使用して、1行でネイティブにこれを行うことができます。

_public <T> void genericMethod(T t) {
    if (t instanceof Set<?>) {
        Set<?> set = (Set<?>) t;
        if (set.stream().allMatch(String.class:isInstance)) {
            Set<String> strs = (Set<String>) set;
            // Do something with strs here
        }
    }
}
_

Java 7以前

Java 7以前では、反復と型チェックを使用する必要があります。

_public <T> void genericMethod(T t) {
    Set<String> strs = new HashSet<String>();
    Set<?> tAsSet;
    if (t instanceof Set<?>) {
        tAsSet = (Set<?>) t;
        for (Object obj : tAsSet) {
            if (obj instanceof String) {
                strs.add((String) obj);
            }
        }
        // Do something with strs here
    } else {
        // Throw an exception or log a warning or something.
    }
}
_

グアバ

以下のMark Petersのコメントによると、Guavaには、プロジェクトに追加できる場合にこれを行うメソッドもあります。

_public <T> void genericMethod(T t) {
    if (t instanceof Set<?>) {
        Set<?> set = (Set<?>) t;
        if (Iterables.all(set, Predicates.instanceOf(String.class))) {
            Set<String> strs = (Set<String>) set;
            // Do something with strs here
        }
    }
}
_

ステートメントIterables.all(set, Predicates.instanceOf(String.class))は、本質的に_set instanceof Set<String>_と同じものです。

18
Brian

残念ながら、Javaにはそのオプションはありません。 Javaでは、List<String>List<Integer>の間にruntimeの違いはありません。 add()IntegerList<String>に決してしないことを保証するのはcompilerです。そのコンパイラーの施行は厳密ではないので、チェックされていないキャストで「合法的に」そのような憎悪を行うことができます。

全体として、(ほぼ)実行時の型の識別の問題については、実際に何であるかについてList<String>を取得する必要があります。それは、生のListです。それはtype erasureと呼ばれます。

とはいえ、Listのコンテンツのタイプを調べることを妨げるものは何もありません。

public boolean isListOf(List<?> list, Class<?> c) {
    for (Object o : list) {
        if (!c.isInstance(o)) return false;
    }

    return true;
}

この方法を使用するには:

    // ...
    if (genericObject instanceof List<?>) {
        if (isListOf((List<?>) genericObject, String.class)) {
            @SuppressWarnings("unchecked")
            List<String> strings = (List<String>) genericObject;
        }
    }

興味深い観察:listが空の場合、メソッドは指定されたすべての型に対してtrueを返します。実際、空のList<String>と空のList<Integer>whatsoeverの間にランタイムの違いはありません。

9
Saintali