Java:ジェネリック型からクラスリテラルを取得するにはどうすればよいですか?


193

通常、次のようにクラスリテラルを使用する人を見てきました。

Class<Foo> cls = Foo.class;

しかし、タイプが総称型である場合はどうでしょうか(例:リスト)。これは正常に機能しますが、リストをパラメーター化する必要があるため、警告が表示されます。

Class<List> cls = List.class

では、なぜ追加しないの<?>ですか?まあ、これは型の不一致エラーを引き起こします:

Class<List<?>> cls = List.class

私はこのようなものがうまくいくと思っていましたが、これは単なる旧式の構文エラーです:

Class<List<Foo>> cls = List<Foo>.class

Class<List<Foo>>クラスリテラルを使用するなど、静的に取得するにはどうすればよいですか?

私は可能性が使用し@SuppressWarnings("unchecked")、最初の例では、リストの非パラメータ化の使用によって引き起こされた警告を取り除くためにClass<List> cls = List.class、私はむしろないと思います。

助言がありますか?

回答:


160

タイプ消去のためにできません。

Javaジェネリックは、オブジェクトキャストの構文糖に過ぎません。実証するには:

List<Integer> list1 = new ArrayList<Integer>();
List<String> list2 = (List<String>)list1;
list2.add("foo"); // perfectly legal

ジェネリック型情報が実行時に保持される唯一のインスタンスは、Field.getGenericType()リフレクションを介してクラスのメンバーに問い合わせる場合です。

これがすべてObject.getClass()この署名がある理由です。

public final native Class<?> getClass();

重要な部分はClass<?>です。

別の言い方をすると、Java Generics FAQから

具体的なパラメーター化された型のクラスリテラルがないのはなぜですか?

パラメータ化された型には、正確な実行時の型表現がないためです。

クラスリテラルはClass 、特定の型を表すオブジェクトを表します。たとえば、クラスリテラル String.classは、Class 型を表すオブジェクトを示し、オブジェクトで メソッドが呼び出された ときに返されるオブジェクトStringと同じです 。クラスリテラルは、実行時の型チェックとリフレクションに使用できます。ClassgetClassString

パラメータ化された型は、型消去と呼ばれるプロセスでのコンパイル中にバイトコードに変換されると、型引数を失います。型消去の副作用として、ジェネリック型のすべてのインスタンス化は同じランタイム表現、つまり対応するraw型のインスタンス化を共有します。つまり、パラメータ化された型には、それ自体の型表現はありません。したがって、その ようなオブジェクトが存在しないためList<String>.classList<Long>.classList<?>.class、などのクラスリテラルを作成しても意味がありませんClass。rawタイプのみが、 そのランタイムタイプを表すオブジェクトをList持っていClassます。と呼ばれ List.classます。


12
List<Integer> list1 = new ArrayList<Integer>(); List<String> list2 = (List<String>)list1; list2.add("foo"); // perfectly legal Javaではこれを実行できません。型の不一致コンパイルエラーが発生します。
DhafirNz

4
だから...もし必要ならどうすればいい?
クリストファーフランシスコ

2
いつでもコンパイラーをだますことができますList<String> list2 = (List<String>) (Object) list1;
kftse '21

17
さらに別の「私はC#でのみ機能し、Javaでは機能しない」JSONオブジェクトを逆シリアル化していて、typeof(List <MyClass>)がC#で完全に正常に動作しますが、List <MyClass> .classはJavaの構文エラーです。はい、Cletusが書いたように、論理的な説明がいつものようにありますが、なぜこれらすべてがC#で機能するのか、いつも疑問に思っています。
くそー野菜

2
完全に合法だとはどういう意味ですか?コードのその部分はコンパイルされませんか?
Eduardo Dennis

63

パラメータ化された型のクラスリテラルはありませんが、これらの型を正しく定義するTypeオブジェクトがあります。

java.lang.reflect.ParameterizedType- http://java.sun.com/j2se/1.5.0/docs/api/java/lang/reflect/ParameterizedType.htmlを参照してください

GoogleのGsonライブラリは、パラメーター化された型を簡単に生成できるTypeTokenクラスを定義し、それを使用して複雑なパラメーター化された型を持つjsonオブジェクトを汎用的な方法で指定します。あなたの例ではあなたが使うでしょう:

Type typeOfListOfFoo = new TypeToken<List<Foo>>(){}.getType()

TypeTokenクラスとGsonクラスのjavadocへのリンクを投稿するつもりでしたが、私は新しいユーザーなので、Stack Overflowでは複数のリンクを投稿できません。Google検索を使用して簡単に見つけることができます


1
これで私はジェネリックEのクラスを作成し、clzz = new TypeToken<E>(){}.getRawType();後で同様に構造化された列挙型を繰り返し処理しclzz.getEnumConstants()、最後にrefectionを使用してメンバーメソッドを呼び出すMethod method = clzz.getDeclaredMethod("getSomeFoo");ことができました。ありがとうございました!
Naruto Sempai 2017

57

あなたはダブルキャストでそれを管理することができます:

@SuppressWarnings("unchecked") Class<List<Foo>> cls = (Class<List<Foo>>)(Object)List.class


2
2番目のキャストをからObjectに変更Classすることで、(無意味な)チェックされたランタイムキャストのオーバーヘッドをおそらく節約できます。
Clashsoft 2016

2
使用@Clashsoft Classの代わりに、Objectあなたが示唆として、より意味のようだが、それはする必要は削除されません@SuppressWarnings("unchecked")注釈を、それも新しい警告を追加:Class is a raw type. References to generic type Class<T> should be parameterized
Ortomala Lokni

10
使用できますClass<?>(Class<List<Foo>>)(Class<?>)List.class
Devstr

@Devstr試してみると正しいと思います...(Object)または(Class <?>)を使用するための引数は何ですか?
cellepo 2017年

2
この答えは全く無意味です。OPがクラスパスをパラメーター化したかったのは、彼がunchecked警告を受けたからです。この答えはそれを変更/改善するものではありません。OPは彼の質問で彼は使いたくないと述べていますSuppressWarnings...
Spenhouet

6

クレタスの答えを詳しく説明するために、実行時にジェネリック型のすべてのレコードが削除されます。ジェネリックスはコンパイラーでのみ処理され、追加のタイプセーフを提供するために使用されます。これらは、コンパイラーが適切な場所に型キャストを挿入できるようにするための単なる省略形です。たとえば、以前は次のようにする必要があります。

List x = new ArrayList();
x.add(new SomeClass());
Iterator i = x.iterator();
SomeClass z = (SomeClass) i.next();

なる

List<SomeClass> x = new ArrayList<SomeClass>();
x.add(new SomeClass());
Iterator<SomeClass> i = x.iterator();
SomeClass z = i.next();

これにより、コンパイラーはコンパイル時にコードをチェックできますが、実行時にはまだ最初の例のように見えます。


追加の説明をありがとう-ジェネリックの私の理解は非常に明確になったので、ジェネリックはランタイムメカニズムではないことがわかりました。:)
Tom

2
私の意見では、これは、ジェネリックがSunによって平凡な方法で実装されたことを意味します。うまくいけば、Oracleはいつかこれを修正します。C#のジェネリックの実装ははるかに優れています(Andersは神のようです)
Marcel Valdez Orozco '25

1
@MarcelValdezOrozco AFAIK、Javaでは、古いコード(1.5以前)が新しいJVMで問題なく動作することを望んでいたため、Javaで実装しました。互換性を気にするのは非常に賢い設計決定だと思われます。そこには平凡なものはないと思います。
peter.petrov 2015

3

Javaのジェネリックよくある質問とそのためにもcletus' 解答持つにはポイントが存在しないような音はClass<List<T>>、しかし、本当の問題は、これは非常に危険であるということです。

@SuppressWarnings("unchecked")
Class<List<String>> stringListClass = (Class<List<String>>) (Class<?>) List.class;

List<Integer> intList = new ArrayList<>();
intList.add(1);
List<String> stringList = stringListClass.cast(intList);
// Surprise!
String firstElement = stringList.get(0);

cast()それが安全であるかのように見えますが、実際にそれがすべてでは安全ではありません。


List<?>.class= が存在しない場所は取得できませんがClass<List<?>>Class引数のジェネリック型に基づいて型を決定するメソッドがある場合、これは非常に役立ちます。

以下のためにgetClass()そこにあるJDK-6184881は、ワイルドカードを使用してスイッチに要求し、この変更は、(非常にすぐに)実行されますように、それは前のコードとの互換性がないので、しかし、それは(参照見えないこのコメントを)。


2

よくわかるように、それは消去されます。しかし、クラス階層で型が明示的に言及されている状況では、それを知ることができます。

import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.stream.Collectors;

public abstract class CaptureType<T> {
    /**
     * {@link java.lang.reflect.Type} object of the corresponding generic type. This method is useful to obtain every kind of information (including annotations) of the generic type.
     *
     * @return Type object. null if type could not be obtained (This happens in case of generic type whose information cant be obtained using Reflection). Please refer documentation of {@link com.types.CaptureType}
     */
    public Type getTypeParam() {
        Class<?> bottom = getClass();
        Map<TypeVariable<?>, Type> reifyMap = new LinkedHashMap<>();

        for (; ; ) {
            Type genericSuper = bottom.getGenericSuperclass();
            if (!(genericSuper instanceof Class)) {
                ParameterizedType generic = (ParameterizedType) genericSuper;
                Class<?> actualClaz = (Class<?>) generic.getRawType();
                TypeVariable<? extends Class<?>>[] typeParameters = actualClaz.getTypeParameters();
                Type[] reified = generic.getActualTypeArguments();
                assert (typeParameters.length != 0);
                for (int i = 0; i < typeParameters.length; i++) {
                    reifyMap.put(typeParameters[i], reified[i]);
                }
            }

            if (bottom.getSuperclass().equals(CaptureType.class)) {
                bottom = bottom.getSuperclass();
                break;
            }
            bottom = bottom.getSuperclass();
        }

        TypeVariable<?> var = bottom.getTypeParameters()[0];
        while (true) {
            Type type = reifyMap.get(var);
            if (type instanceof TypeVariable) {
                var = (TypeVariable<?>) type;
            } else {
                return type;
            }
        }
    }

    /**
     * Returns the raw type of the generic type.
     * <p>For example in case of {@code CaptureType<String>}, it would return {@code Class<String>}</p>
     * For more comprehensive examples, go through javadocs of {@link com.types.CaptureType}
     *
     * @return Class object
     * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
     * @see com.types.CaptureType
     */
    public Class<T> getRawType() {
        Type typeParam = getTypeParam();
        if (typeParam != null)
            return getClass(typeParam);
        else throw new RuntimeException("Could not obtain type information");
    }


    /**
     * Gets the {@link java.lang.Class} object of the argument type.
     * <p>If the type is an {@link java.lang.reflect.ParameterizedType}, then it returns its {@link java.lang.reflect.ParameterizedType#getRawType()}</p>
     *
     * @param type The type
     * @param <A>  type of class object expected
     * @return The Class<A> object of the type
     * @throws java.lang.RuntimeException If the type is a {@link java.lang.reflect.TypeVariable}. In such cases, it is impossible to obtain the Class object
     */
    public static <A> Class<A> getClass(Type type) {
        if (type instanceof GenericArrayType) {
            Type componentType = ((GenericArrayType) type).getGenericComponentType();
            Class<?> componentClass = getClass(componentType);
            if (componentClass != null) {
                return (Class<A>) Array.newInstance(componentClass, 0).getClass();
            } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
        } else if (type instanceof Class) {
            Class claz = (Class) type;
            return claz;
        } else if (type instanceof ParameterizedType) {
            return getClass(((ParameterizedType) type).getRawType());
        } else if (type instanceof TypeVariable) {
            throw new RuntimeException("The type signature is erased. The type class cant be known by using reflection");
        } else throw new UnsupportedOperationException("Unknown class: " + type.getClass());
    }

    /**
     * This method is the preferred method of usage in case of complex generic types.
     * <p>It returns {@link com.types.TypeADT} object which contains nested information of the type parameters</p>
     *
     * @return TypeADT object
     * @throws java.lang.RuntimeException If the type information cant be obtained. Refer documentation of {@link com.types.CaptureType}
     */
    public TypeADT getParamADT() {
        return recursiveADT(getTypeParam());
    }

    private TypeADT recursiveADT(Type type) {
        if (type instanceof Class) {
            return new TypeADT((Class<?>) type, null);
        } else if (type instanceof ParameterizedType) {
            ArrayList<TypeADT> generic = new ArrayList<>();
            ParameterizedType type1 = (ParameterizedType) type;
            return new TypeADT((Class<?>) type1.getRawType(),
                    Arrays.stream(type1.getActualTypeArguments()).map(x -> recursiveADT(x)).collect(Collectors.toList()));
        } else throw new UnsupportedOperationException();
    }

}

public class TypeADT {
    private final Class<?> reify;
    private final List<TypeADT> parametrized;

    TypeADT(Class<?> reify, List<TypeADT> parametrized) {
        this.reify = reify;
        this.parametrized = parametrized;
    }

    public Class<?> getRawType() {
        return reify;
    }

    public List<TypeADT> getParameters() {
        return parametrized;
    }
}

そして今、あなたは次のようなことをすることができます:

static void test1() {
        CaptureType<String> t1 = new CaptureType<String>() {
        };
        equals(t1.getRawType(), String.class);
    }

    static void test2() {
        CaptureType<List<String>> t1 = new CaptureType<List<String>>() {
        };
        equals(t1.getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), String.class);
    }


    private static void test3() {
            CaptureType<List<List<String>>> t1 = new CaptureType<List<List<String>>>() {
            };
            equals(t1.getParamADT().getRawType(), List.class);
        equals(t1.getParamADT().getParameters().get(0).getRawType(), List.class);
    }

    static class Test4 extends CaptureType<List<String>> {
    }

    static void test4() {
        Test4 test4 = new Test4();
        equals(test4.getParamADT().getRawType(), List.class);
    }

    static class PreTest5<S> extends CaptureType<Integer> {
    }

    static class Test5 extends PreTest5<Integer> {
    }

    static void test5() {
        Test5 test5 = new Test5();
        equals(test5.getTypeParam(), Integer.class);
    }

    static class PreTest6<S> extends CaptureType<S> {
    }

    static class Test6 extends PreTest6<Integer> {
    }

    static void test6() {
        Test6 test6 = new Test6();
        equals(test6.getTypeParam(), Integer.class);
    }



    class X<T> extends CaptureType<T> {
    }

    class Y<A, B> extends X<B> {
    }

    class Z<Q> extends Y<Q, Map<Integer, List<List<List<Integer>>>>> {
    }

    void test7(){
        Z<String> z = new Z<>();
        TypeADT param = z.getParamADT();
        equals(param.getRawType(), Map.class);
        List<TypeADT> parameters = param.getParameters();
        equals(parameters.get(0).getRawType(), Integer.class);
        equals(parameters.get(1).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getRawType(), List.class);
        equals(parameters.get(1).getParameters().get(0).getParameters().get(0).getParameters().get(0).getRawType(), Integer.class);
    }




    static void test8() throws IllegalAccessException, InstantiationException {
        CaptureType<int[]> type = new CaptureType<int[]>() {
        };
        equals(type.getRawType(), int[].class);
    }

    static void test9(){
        CaptureType<String[]> type = new CaptureType<String[]>() {
        };
        equals(type.getRawType(), String[].class);
    }

    static class SomeClass<T> extends CaptureType<T>{}
    static void test10(){
        SomeClass<String> claz = new SomeClass<>();
        try{
            claz.getRawType();
            throw new RuntimeException("Shouldnt come here");
        }catch (RuntimeException ex){

        }
    }

    static void equals(Object a, Object b) {
        if (!a.equals(b)) {
            throw new RuntimeException("Test failed. " + a + " != " + b);
        }
    }

詳細はこちら。しかし、ここでも取得することはほとんど不可能です。

class SomeClass<T> extends CaptureType<T>{}
SomeClass<String> claz = new SomeClass<>();

消去されます。


これは、JAX-RSで使用される回避策でもあります。GenericEntityGenericType
HeinBlöd16年

1

クラスリテラルにはジェネリック型情報がないという公開された事実により、すべての警告を取り除くことは不可能だと思います。ある意味では、使用Class<Something>は、ジェネリック型を指定せずにコレクションを使用することと同じです。私が思いつくことができた最高のものは:

private <C extends A<C>> List<C> getList(Class<C> cls) {
    List<C> res = new ArrayList<C>();
    // "snip"... some stuff happening in here, using cls
    return res;
}

public <C extends A<C>> List<A<C>> getList() {
    return getList(A.class);
}

1

ヘルパーメソッドを使用@SuppressWarnings("unchecked")して、クラス全体を取り除くことができます。

@SuppressWarnings("unchecked")
private static <T> Class<T> generify(Class<?> cls) {
    return (Class<T>)cls;
}

それからあなたは書くことができます

Class<List<Foo>> cls = generify(List.class);

他の使用例は

  Class<Map<String, Integer>> cls;

  cls = generify(Map.class);

  cls = TheClass.<Map<String, Integer>>generify(Map.class);

  funWithTypeParam(generify(Map.class));

public void funWithTypeParam(Class<Map<String, Integer>> cls) {
}

ただし、これが実際に役立つことはめったになく、このメソッドの使用はコンパイラーのタイプチェックを無効にするため、パブリックにアクセスできる場所に実装することはお勧めしません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.