Holen Sie sich den generischen Typ von java.util.List

262

Ich habe;

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

Gibt es eine (einfache) Möglichkeit, den generischen Typ der Liste abzurufen?

Thizzer
quelle
Um ein List-Objekt programmgesteuert untersuchen zu können und seinen generischen Typ zu sehen. Eine Methode möchte möglicherweise Objekte basierend auf dem generischen Typ der Sammlung einfügen. Dies ist in Sprachen möglich, die zur Laufzeit Generika anstelle der Kompilierungszeit implementieren.
Steve Kuo
4
Richtig - die einzige Möglichkeit, die Laufzeiterkennung zuzulassen, besteht in der Unterklassifizierung: Sie können den generischen Typ tatsächlich erweitern und dann mithilfe der Reflection-Find-Typdeklaration den verwendeten Subtyp verwenden. Dies ist einiges an Nachdenken, aber möglich. Leider gibt es keine einfache Möglichkeit, die Verwendung einer generischen Unterklasse durchzusetzen.
StaxMan
1
Sicherlich enthält stringList Strings und integerList-Ganzzahlen? Warum es komplizierter machen?
Ben Thurley

Antworten:

408

Wenn dies tatsächlich Felder einer bestimmten Klasse sind, können Sie sie mit ein wenig Reflexionshilfe erhalten:

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.
    }
}

Sie können dies auch für Parametertypen und Rückgabetypen von Methoden tun.

Wenn sie sich jedoch im selben Bereich der Klasse / Methode befinden, in der Sie sie kennen müssen, ist es sinnlos, sie zu kennen, da Sie sie bereits selbst deklariert haben.

BalusC
quelle
17
Es gibt sicherlich Situationen, in denen dies oben nützlich ist. In beispielsweise konfigurationslosen ORM-Frameworks.
BalusC
3
..die Klasse # getDeclaredFields () verwenden kann, um alle Felder abzurufen, ohne den Feldnamen kennen zu müssen.
BalusC
1
BalusC: Das klingt für mich wie ein Injektionsrahmen. Das ist die Art von Verwendung, die ich sowieso gemeint habe.
Falstro
1
@loolooyyyy Anstatt TypeLiteral zu verwenden, empfehle ich die Verwendung von TypeToken aus Guava. github.com/google/guava/wiki/ReflectionExplained
Babyburger
1
@Oleg Ihre Variable verwendet <?>(Platzhaltertyp) anstelle von <Class>(Klassentyp). Ersetzen Sie Ihre <?>durch <Class>oder was auch immer <E>.
BalusC
19

Sie können dasselbe auch für Methodenparameter tun:

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
tsaixingwei
quelle
In method.getGenericParameterTypes (); Was ist Methode?
Neo Ravi
18

Kurze Antwort: nein.

Dies ist wahrscheinlich ein Duplikat, kann momentan kein passendes finden.

Java verwendet eine sogenannte Typlöschung, was bedeutet, dass zur Laufzeit beide Objekte gleichwertig sind. Der Compiler weiß, dass die Listen Ganzzahlen oder Zeichenfolgen enthalten, und kann daher eine typsichere Umgebung aufrechterhalten. Diese Informationen gehen zur Laufzeit (auf Objektinstanzbasis) verloren, und die Liste enthält nur 'Objekte'.

Sie können ein wenig über die Klasse herausfinden, von welchen Typen sie möglicherweise parametrisiert wird, aber normalerweise ist dies nur alles, was "Objekt" erweitert, dh alles. Wenn Sie einen Typ definieren als

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

AClass.class enthält nur die Tatsache, dass der Parameter A durch MyClass begrenzt ist, aber darüber hinaus gibt es keine Möglichkeit zu sagen.

falstro
quelle
1
Dies ist der Fall, wenn die konkrete Klasse des Generikums nicht angegeben ist. in seinem Beispiel deklariert er die Listen explizit als List<Integer>und List<String>; In diesen Fällen gilt die Typlöschung nicht.
Haroldo_OK
13

Der generische Typ einer Sammlung sollte nur dann eine Rolle spielen, wenn sie tatsächlich Objekte enthält, oder? Ist es nicht einfacher, einfach zu tun:

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

Es gibt keinen generischen Typ zur Laufzeit, aber die Objekte zur Laufzeit haben garantiert den gleichen Typ wie das deklarierte generische. Es ist also einfach genug, die Klasse des Elements zu testen, bevor wir es verarbeiten.

Sie können die Liste auch einfach verarbeiten, um Mitglieder des richtigen Typs zu erhalten, andere zu ignorieren (oder sie anders zu verarbeiten).

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());

Auf diese Weise erhalten Sie eine Liste aller Elemente, deren Klassen Unterklassen waren, Numberdie Sie dann nach Bedarf verarbeiten können. Der Rest der Elemente wurde in andere Listen herausgefiltert. Da sie in der Karte enthalten sind, können Sie sie wie gewünscht verarbeiten oder ignorieren.

Wenn Sie Elemente anderer Klassen vollständig ignorieren möchten, wird dies viel einfacher:

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

Sie können sogar eine Dienstprogrammmethode erstellen, um sicherzustellen, dass eine Liste NUR die Elemente enthält, die einer bestimmten Klasse entsprechen:

public <V> List<V> getTypeSafeItemList(Collection<Object> input, Class<V> cls) {
    return input.stream()
            .filter(cls::isInstance)
            .map(cls::cast)
            .collect(Collectors.toList());
}
Steve K.
quelle
5
Und wenn Sie eine leere Sammlung haben? Oder wenn Sie eine Sammlung <Objekt> mit einer Ganzzahl und einem String haben?
Falci
Der Vertrag für die Klasse kann erfordern, dass nur Listen der endgültigen Objekte übergeben werden und dass der Methodenaufruf null zurückgibt, wenn die Liste null oder leer ist oder wenn der Listentyp ungültig ist.
ggb667
1
Im Fall einer leeren Sammlung ist es sicher, zur Laufzeit jedem Listentyp zuzuweisen, da nichts darin enthalten ist. Nach dem Löschen des Typs ist eine Liste eine Liste eine Liste. Aus diesem Grund kann ich mir keinen Fall vorstellen, in dem der Typ einer leeren Sammlung von Bedeutung ist.
Steve K
Nun, es könnte "wichtig" sein, wenn Sie die Referenz in einem Thread festhalten und verarbeiten, sobald Objekte in einem Producer-Consumer-Szenario angezeigt werden. Wenn die Liste die falschen Objekttypen hätte, könnten schlimme Dinge passieren. Ich denke, um zu verhindern, dass Sie die Verarbeitung stoppen müssen, indem Sie schlafen, bis mindestens ein Element in der Liste vorhanden ist, bevor Sie fortfahren.
ggb667
Wenn Sie ein solches Produzenten- / Konsumentenszenario haben, ist es ohnehin ein schlechtes Design, zu planen, dass die Liste einen bestimmten generischen Typ hat, ohne sicher zu sein, was in die Liste aufgenommen werden könnte. Stattdessen würden Sie es als eine Sammlung von Objects deklarieren und wenn Sie sie aus der Liste ziehen, würden Sie sie einem geeigneten Handler basierend auf ihrem Typ geben.
Steve K
11

So finden Sie den generischen Typ eines Felds:

((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
Mohsen Kashi
quelle
10

Wenn Sie den generischen Typ eines zurückgegebenen Typs abrufen müssen, habe ich diesen Ansatz verwendet, um Methoden in einer Klasse zu finden, die a zurückgegeben hat, Collectionund dann auf ihre generischen Typen zuzugreifen:

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]);
                    }
                }
            }
        }

    }

}

Dies gibt aus:

Der generische Typ ist die Klasse java.lang.String

Aram Kocharyan
quelle
6

Erweitern Sie die Antwort von Steve 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);
   }
}
ggb667
quelle
Gleiche Probleme wie bei Steve Ks Antwort: Was ist, wenn die Liste leer ist? Was ist, wenn die Liste vom Typ <Objekt> ist, der einen String und eine Ganzzahl enthält? Ihr Code bedeutet, dass Sie nur dann weitere Zeichenfolgen hinzufügen können, wenn das erste Element in der Liste eine Zeichenfolge ist und überhaupt keine Ganzzahlen ...
Subrunner
Wenn die Liste leer ist, haben Sie kein Glück. Wenn die Liste Objekt ist und tatsächlich ein Objekt enthält, sind Sie in Ordnung, aber wenn sie eine Mischung aus Dingen enthält, haben Sie kein Glück. Löschen bedeutet, dass dies so gut wie möglich ist. Löschung fehlt meiner Meinung nach. Die Auswirkungen sind sofort schlecht verstanden und nicht ausreichend, um die interessanten und gewünschten Anwendungsfälle von typischer Bedeutung anzusprechen. Nichts hindert daran, eine Klasse zu erstellen, die gefragt werden kann, welchen Typ sie verwendet, aber so funktionieren die integrierten Klassen einfach nicht. Sammlungen sollten wissen, was sie enthalten, aber leider ...
ggb667
4

Zur Laufzeit können Sie nicht.

Jedoch durch Reflexion der Typparameter sind zugänglich. Versuchen

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

Die Methode getGenericType()gibt ein Type-Objekt zurück. In diesem Fall handelt es sich um eine Instanz von ParametrizedType, die wiederum Methoden enthält getRawType()(die List.classin diesem Fall enthalten) und getActualTypeArguments()die ein Array zurückgibt (in diesem Fall von der Länge eins, das entweder String.classoder enthält Integer.class).

Scott Morrison
quelle
2
und funktioniert es für Parameter, die von einer Methode anstelle von Feldern einer Klasse empfangen werden?
öffnet
@opensas Mit können method.getGenericParameterTypes()Sie die deklarierten Typen der Parameter einer Methode abrufen .
Radiodef
4

Hatte das gleiche Problem, aber ich habe stattdessen instanceof verwendet. Habe es so gemacht:

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");
       }
    }
}

Dies beinhaltet die Verwendung von ungeprüften Casts. Tun Sie dies also nur, wenn Sie wissen, dass es sich um eine Liste handelt und welcher Typ es sein kann.

Andy
quelle
3

Im Allgemeinen unmöglich, weil List<String>und List<Integer>die gleiche Laufzeitklasse teilen.

Möglicherweise können Sie jedoch über den deklarierten Typ des Felds nachdenken, in dem sich die Liste befindet (wenn der deklarierte Typ selbst nicht auf einen Typparameter verweist, dessen Wert Sie nicht kennen).

Meriton
quelle
2

Wie andere gesagt haben, ist die einzig richtige Antwort nein, der Typ wurde gelöscht.

Wenn die Liste eine Anzahl von Elementen ungleich Null enthält, können Sie den Typ des ersten Elements untersuchen (z. B. mithilfe der Methode getClass). Das sagt Ihnen nicht den generischen Typ der Liste, aber es wäre vernünftig anzunehmen, dass der generische Typ eine Oberklasse der Typen in der Liste war.

Ich würde den Ansatz nicht befürworten, aber in einer Bindung könnte es nützlich sein.

Chris Arguin
quelle
Dies ist der Fall, wenn die konkrete Klasse des Generikums nicht angegeben ist. in seinem Beispiel deklariert er die Listen explizit als List<Integer>und List<String>; In diesen Fällen gilt die Typlöschung nicht.
Haroldo_OK
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();
    }
}
Ashish
quelle
1
was es ist getFieldGenericTypefieldGenericType, kann nicht gefunden werden
shareef
0

Verwenden Sie Reflection, um die Fieldfür diese zu erhalten, dann können Sie einfach tun: field.genericTypeum den Typ zu erhalten, der auch die Informationen über generische enthält.

Siddharth Shishulkar
quelle
Erwägen Sie das Hinzufügen eines Beispielcodes, andernfalls ist dies eher für einen Kommentar und nicht für eine Antwort geeignet
Alexey Kamenskiy,