java.util.Listのジェネリック型を取得します


262

私が持っています;

List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();

リストのジェネリック型を取得する(簡単な)方法はありますか?


プログラムでListオブジェクトを検査し、そのジェネリック型を確認できるようにするため。メソッドは、コレクションのジェネリック型に基づいてオブジェクトを挿入したい場合があります。これは、コンパイル時ではなく実行時にジェネリックを実装する言語で可能です。
スティーブKuo

4
正しい-ラン​​タイム検出を可能にする唯一の方法は、サブクラス化によるものです。実際にジェネリック型を拡張してから、サブタイプが使用したリフレクション検索型宣言を使用できます。これはかなりの反射ですが、可能です。残念ながら、一般的なサブクラスを使用する必要があることを強制する簡単な方法はありません。
StaxMan

1
確かにstringListには文字列とintegerList整数が含まれていますか?なぜもっと複雑にするのですか?
Ben Thurley 2014

回答:


408

それらが実際に特定のクラスのフィールドである場合は、リフレクションを少し使ってそれらを取得できます。

package test;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

public class Test {

    List<String> stringList = new ArrayList<String>();
    List<Integer> integerList = new ArrayList<Integer>();

    public static void main(String... args) throws Exception {
        Field stringListField = Test.class.getDeclaredField("stringList");
        ParameterizedType stringListType = (ParameterizedType) stringListField.getGenericType();
        Class<?> stringListClass = (Class<?>) stringListType.getActualTypeArguments()[0];
        System.out.println(stringListClass); // class java.lang.String.

        Field integerListField = Test.class.getDeclaredField("integerList");
        ParameterizedType integerListType = (ParameterizedType) integerListField.getGenericType();
        Class<?> integerListClass = (Class<?>) integerListType.getActualTypeArguments()[0];
        System.out.println(integerListClass); // class java.lang.Integer.
    }
}

また、パラメーターの型とメソッドの戻り値の型についても行うことができます。

しかし、それらがクラス/メソッドの同じスコープ内にあり、それらについて知る必要がある場合は、すでに自分で宣言しているため、それらを知る意味はありません。


17
確かに、これが役に立つ場合もあります。たとえば、設定不要のORMフレームワーク。
BalusC 2009

3
..これはClass#getDeclaredFields()を使用して、フィールド名を知らなくてもすべてのフィールドを取得できます。
BalusC 2009

1
BalusC:それは私にとってインジェクションフレームワークのように聞こえます。それはとにかく私が意図した種類の使用法です。
falstro

1
@loolooyyyy TypeLiteralを使用するのではなく、GuavaのTypeTokenを使用することをお勧めします。github.com/google/guava/wiki/ReflectionExplained
ベイビー

1
@Oleg変数は(クラスタイプ)<?>ではなく(ワイルドカードタイプ)を使用してい<Class>ます。あなた交換する<?>ことにより、<Class>または何を<E>
BalusC

19

メソッドパラメータについても同じことができます。

Type[] types = method.getGenericParameterTypes();
//Now assuming that the first parameter to the method is of type List<Integer>
ParameterizedType pType = (ParameterizedType) types[0];
Class<?> clazz = (Class<?>) pType.getActualTypeArguments()[0];
System.out.println(clazz); //prints out java.lang.Integer

method.getGenericParameterTypes(); メソッドとは何ですか?
ネオラヴィ

18

短い答え:いいえ。

これはおそらく重複です。現在適切なものを見つけることができません。

Javaは型消去と呼ばれるものを使用します。これは、実行時に両方のオブジェクトが同等であることを意味します。コンパイラは、リストに整数または文字列が含まれていることを認識しているため、タイプセーフな環境を維持できます。この情報は実行時に(オブジェクトインスタンスベースで)失われ、リストには「オブジェクト」のみが含まれます。

あなたはクラスについて少し知ることができます、それがどのタイプによってパラメータ化されるかもしれませんが、通常これは単に「オブジェクト」を拡張するもの、すなわち何でもです。タイプを次のように定義した場合

class <A extends MyClass> AClass {....}

AClass.classには、パラメーターAがMyClassによってバインドされているという事実のみが含まれますが、それ以上のことを伝える方法はありません。


1
ジェネリックの具象クラスが指定されていない場合も同様です。彼の例では、リストをList<Integer>and として明示的に宣言していますList<String>。これらの場合、型消去は適用されません。
Haroldo_OK 2018年

13

コレクションのジェネリック型は、実際にオブジェクトが含まれている場合にのみ問題になるはずですよね?だから、それを行うのは簡単ではありませんか?

Collection<?> myCollection = getUnknownCollectionFromSomewhere();
Class genericClass = null;
Iterator it = myCollection.iterator();
if (it.hasNext()){
    genericClass = it.next().getClass();
}
if (genericClass != null) { //do whatever we needed to know the type for

実行時のジェネリック型などはありませんが、実行時の内部のオブジェクトは宣言されたジェネリックと同じ型であることが保証されているため、アイテムのクラスを処理する前にテストするだけで十分簡単です。

他にできることは、リストを処理して正しいタイプのメンバーを取得し、他のメンバーを無視する(またはそれらを異なる方法で処理する)ことです。

Map<Class<?>, List<Object>> classObjectMap = myCollection.stream()
    .filter(Objects::nonNull)
    .collect(Collectors.groupingBy(Object::getClass));

// Process the list of the correct class, and/or handle objects of incorrect
// class (throw exceptions, etc). You may need to group subclasses by
// filtering the keys. For instance:

List<Number> numbers = classObjectMap.entrySet().stream()
        .filter(e->Number.class.isAssignableFrom(e.getKey()))
        .flatMap(e->e.getValue().stream())
        .map(Number.class::cast)
        .collect(Collectors.toList());

これにより、クラスがサブクラスでNumberあり、必要に応じて処理できるすべてのアイテムのリストが表示されます。残りのアイテムは他のリストにフィルターで除外されました。それらはマップ内にあるため、必要に応じて処理したり、無視したりできます。

他のクラスのアイテムを完全に無視したい場合は、はるかに簡単になります。

List<Number> numbers = myCollection.stream()
    .filter(Number.class::isInstance)
    .map(Number.class::cast)
    .collect(Collectors.toList());

ユーティリティメソッドを作成して、リストに特定のクラスに一致するアイテムのみが含まれるようにすることもできます。

public <V> List<V> getTypeSafeItemList(Collection<Object> input, Class<V> cls) {
    return input.stream()
            .filter(cls::isInstance)
            .map(cls::cast)
            .collect(Collectors.toList());
}

5
そして、あなたが空のコレクションを持っているなら?または、整数と文字列を含むCollection <Object>がある場合は?
ファルシ2013年

クラスのコントラクトでは、最終オブジェクトのリストのみを渡す必要があります。リストがnull、空、またはリストのタイプが無効な場合、メソッド呼び出しはnullを返します。
ggb667 2013

1
空のコレクションの場合、何もないので、実行時に任意のリストタイプに割り当てても安全です。タイプの消去が発生した後、リストはリストです。このため、空のコレクションのタイプが重要になるインスタンスを考えることはできません。
スティーブK

スレッドで参照を保持し、プロデューサーコンシューマーシナリオでオブジェクトが表示され始めたらそれを処理した場合、「問題になる可能性があります」。リストのオブジェクトタイプが間違っていると、問題が発生する可能性があります。続行する前に、リストに少なくとも1つの項目が存在するようになるまでスリープすることで処理を停止する必要があることを防ぐと思います。
ggb667 2016年

そのようなプロデューサー/コンシューマーシナリオがある場合、リストに何を入れることができるかを確認せずにリストを特定のジェネリック型にする計画は、とにかく悪いデザインです。代わりに、それをのコレクションとして宣言し、それらをObjectsのリストから引き出すときに、それらを型に基づいて適切なハンドラーに渡します。
スティーブK

11

1つのフィールドのジェネリック型を見つけるには:

((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()

10

返された型のジェネリック型を取得する必要がある場合は、aを返したクラス内のメソッドを見つけて、Collectionそのジェネリック型にアクセスする必要があるときに、このアプローチを使用しました。

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;

public class Test {

    public List<String> test() {
        return null;
    }

    public static void main(String[] args) throws Exception {

        for (Method method : Test.class.getMethods()) {
            Class returnClass = method.getReturnType();
            if (Collection.class.isAssignableFrom(returnClass)) {
                Type returnType = method.getGenericReturnType();
                if (returnType instanceof ParameterizedType) {
                    ParameterizedType paramType = (ParameterizedType) returnType;
                    Type[] argTypes = paramType.getActualTypeArguments();
                    if (argTypes.length > 0) {
                        System.out.println("Generic type is " + argTypes[0]);
                    }
                }
            }
        }

    }

}

これは出力します:

ジェネリック型はjava.lang.Stringクラスです


6

スティーブKの答えを拡張します。

/** 
* Performs a forced cast.  
* Returns null if the collection type does not match the items in the list.
* @param data The list to cast.
* @param listType The type of list to cast to.
*/
static <T> List<? super T> castListSafe(List<?> data, Class<T> listType){
    List<T> retval = null;
    //This test could be skipped if you trust the callers, but it wouldn't be safe then.
    if(data!=null && !data.isEmpty() && listType.isInstance(data.iterator().next().getClass())) {
        @SuppressWarnings("unchecked")//It's OK, we know List<T> contains the expected type.
        List<T> foo = (List<T>)data;
        return retval;
    }
    return retval;
}
Usage:

protected WhateverClass add(List<?> data) {//For fluant useage
    if(data==null) || data.isEmpty(){
       throw new IllegalArgumentException("add() " + data==null?"null":"empty" 
       + " collection");
    }
    Class<?> colType = data.iterator().next().getClass();//Something
    aMethod(castListSafe(data, colType));
}

aMethod(List<Foo> foo){
   for(Foo foo: List){
      System.out.println(Foo);
   }
}

aMethod(List<Bar> bar){
   for(Bar bar: List){
      System.out.println(Bar);
   }
}

Steve Kの回答と同じ問題:リストが空の場合はどうなりますか?リストが文字列と整数を含むタイプ<Object>の場合はどうなりますか?あなたのコードは、リストの最初の項目が文字列で、整数がまったくない場合にのみ、文字列を追加できることを意味します...
subrunner

リストが空の場合は運が悪いです。リストがオブジェクトで、実際にオブジェクトが含まれている場合は問題ありませんが、リストにオブジェクトが混在している場合は、運が悪いです。消去とは、これができる限り良いことを意味します。消去は私の意見に欠けています。それによる影響はすぐに十分に理解されておらず、典型的な関心事の興味深く望ましい使用例に対処するには不十分です。どんな型を取るかを尋ねられるクラスの作成を妨げるものは何もありませんが、それは単に組み込みクラスがどのように機能するかではありません。コレクションは何が含まれているのかを知っている必要がありますが、悲しいかな...
ggb667

4

実行時には、できません。

ただし、リフレクションを介して型パラメーターアクセスできます。試す

for(Field field : this.getDeclaredFields()) {
    System.out.println(field.getGenericType())
}

このメソッドgetGenericType()はTypeオブジェクトを返します。この場合、それはのインスタンスになりParametrizedType、これにはメソッドgetRawType()List.classこの場合はを含む)とgetActualTypeArguments()、配列(この場合、長さ1の、String.classまたはを含むInteger.class)を返します。


2
そしてそれはクラスのフィールドの代わりにメソッドによって受け取られたパラメータのために働きますか?
opensas、

@opensasを使用method.getGenericParameterTypes()して、メソッドのパラメータの宣言された型を取得できます。
Radiodef

4

同じ問題がありましたが、代わりにinstanceofを使用しました。このようにしましたか:

List<Object> listCheck = (List<Object>)(Object) stringList;
    if (!listCheck.isEmpty()) {
       if (listCheck.get(0) instanceof String) {
           System.out.println("List type is String");
       }
       if (listCheck.get(0) instanceof Integer) {
           System.out.println("List type is Integer");
       }
    }
}

これには、チェックされていないキャストの使用が含まれるため、リストであることがわかっている場合、およびそれがどのタイプであるかを知っている場合にのみ、これを実行してください。


3

同じランタイムクラスList<String>List<Integer>共有しているため、通常は不可能です。

ただし、リストを保持するフィールドの宣言された型を反映できる場合があります(宣言された型自体が、値がわからない型パラメーターを参照していない場合)。


2

他の人が言ったように、唯一の正しい答えは「いいえ」で、タイプは消去されました。

リストにゼロ以外の数の要素がある場合、最初の要素のタイプを調べることができます(たとえば、getClassメソッドを使用して)。リストのジェネリック型はわかりませんが、ジェネリック型はリスト内の型のスーパークラスであると想定するのが妥当です。

私はそのアプローチを主張しませんが、束縛の中でそれは役に立つかもしれません。


ジェネリックの具象クラスが指定されていない場合も同様です。彼の例では、リストをList<Integer>and として明示的に宣言していますList<String>。これらの場合、型消去は適用されません。
Haroldo_OK 2018年

2
import org.junit.Assert;
import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class GenericTypeOfCollectionTest {
    public class FormBean {
    }

    public class MyClazz {
        private List<FormBean> list = new ArrayList<FormBean>();
    }

    @Test
    public void testName() throws Exception {
        Field[] fields = MyClazz.class.getFields();
        for (Field field : fields) {
            //1. Check if field is of Collection Type
            if (Collection.class.isAssignableFrom(field.getType())) {
                //2. Get Generic type of your field
                Class fieldGenericType = getFieldGenericType(field);
                //3. Compare with <FromBean>
                Assert.assertTrue("List<FormBean>",
                  FormBean.class.isAssignableFrom(fieldGenericType));
            }
        }
    }

    //Returns generic type of any field
    public Class getFieldGenericType(Field field) {
        if (ParameterizedType.class.isAssignableFrom(field.getGenericType().getClass())) {
            ParameterizedType genericType =
             (ParameterizedType) field.getGenericType();
            return ((Class)
              (genericType.getActualTypeArguments()[0])).getSuperclass();
        }
        //Returns dummy Boolean Class to compare with ValueObject & FormBean
        return new Boolean(false).getClass();
    }
}

1
getFieldGenericTypefieldGenericTypeそれが見つからないものは何
ですか

0

タイプは消去されるため、消去することはできません。http://en.wikipedia.org/wiki/Type_erasureおよびhttp://en.wikipedia.org/wiki/Generics_in_Java#Type_erasureを参照してください


ジェネリックの具象クラスが指定されていない場合も同様です。彼の例では、リストをList<Integer>and として明示的に宣言していますList<String>。これらの場合、型消去は適用されません。
Haroldo_OK 2018年

0

リフレクションを使用しFieldてこれらを取得し、次に行うことができfield.genericTypeます。ジェネリックに関する情報を含む型も取得します。


いくつかのサンプルコードを追加することを検討してください。そうでない場合、これはコメントではなく回答に適しています。
Alexey Kamenskiy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.