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:
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.
@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
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 extendsMyClass>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.
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:
Sie können sogar eine Dienstprogrammmethode erstellen, um sicherzustellen, dass eine Liste NUR die Elemente enthält, die einer bestimmten Klasse entsprechen:
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.
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:
/**
* 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:protectedWhateverClass add(List<?> data){//For fluant useageif(data==null)|| data.isEmpty(){thrownewIllegalArgumentException("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);}}
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).
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)instanceofString){System.out.println("List type is String");}if(listCheck.get(0)instanceofInteger){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.
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).
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.
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;publicclassGenericTypeOfCollectionTest{publicclassFormBean{}publicclassMyClazz{privateList<FormBean> list =newArrayList<FormBean>();}@Testpublicvoid testName()throwsException{Field[] fields =MyClazz.class.getFields();for(Field field : fields){//1. Check if field is of Collection Typeif(Collection.class.isAssignableFrom(field.getType())){//2. Get Generic type of your fieldClass fieldGenericType = getFieldGenericType(field);//3. Compare with <FromBean>Assert.assertTrue("List<FormBean>",FormBean.class.isAssignableFrom(fieldGenericType));}}}//Returns generic type of any fieldpublicClass 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 & FormBeanreturnnewBoolean(false).getClass();}}
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
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.
Antworten:
Wenn dies tatsächlich Felder einer bestimmten Klasse sind, können Sie sie mit ein wenig Reflexionshilfe erhalten:
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.
quelle
<?>
(Platzhaltertyp) anstelle von<Class>
(Klassentyp). Ersetzen Sie Ihre<?>
durch<Class>
oder was auch immer<E>
.Sie können dasselbe auch für Methodenparameter tun:
quelle
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
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.
quelle
List<Integer>
undList<String>
; In diesen Fällen gilt die Typlöschung nicht.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:
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).
Auf diese Weise erhalten Sie eine Liste aller Elemente, deren Klassen Unterklassen waren,
Number
die 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:
Sie können sogar eine Dienstprogrammmethode erstellen, um sicherzustellen, dass eine Liste NUR die Elemente enthält, die einer bestimmten Klasse entsprechen:
quelle
Object
s deklarieren und wenn Sie sie aus der Liste ziehen, würden Sie sie einem geeigneten Handler basierend auf ihrem Typ geben.So finden Sie den generischen Typ eines Felds:
quelle
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,
Collection
und dann auf ihre generischen Typen zuzugreifen:Dies gibt aus:
quelle
Erweitern Sie die Antwort von Steve K:
quelle
Zur Laufzeit können Sie nicht.
Jedoch durch Reflexion der Typparameter sind zugänglich. Versuchen
Die Methode
getGenericType()
gibt ein Type-Objekt zurück. In diesem Fall handelt es sich um eine Instanz vonParametrizedType
, die wiederum Methoden enthältgetRawType()
(dieList.class
in diesem Fall enthalten) undgetActualTypeArguments()
die ein Array zurückgibt (in diesem Fall von der Länge eins, das entwederString.class
oder enthältInteger.class
).quelle
method.getGenericParameterTypes()
Sie die deklarierten Typen der Parameter einer Methode abrufen .Hatte das gleiche Problem, aber ich habe stattdessen instanceof verwendet. Habe es so gemacht:
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.
quelle
Im Allgemeinen unmöglich, weil
List<String>
undList<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).
quelle
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.
quelle
List<Integer>
undList<String>
; In diesen Fällen gilt die Typlöschung nicht.quelle
getFieldGenericTypefieldGenericType
, kann nicht gefunden werdenDer Typ wird gelöscht, sodass Sie dies nicht können. Siehe http://en.wikipedia.org/wiki/Type_erasure und http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure
quelle
List<Integer>
undList<String>
; In diesen Fällen gilt die Typlöschung nicht.Verwenden Sie Reflection, um die
Field
für diese zu erhalten, dann können Sie einfach tun:field.genericType
um den Typ zu erhalten, der auch die Informationen über generische enthält.quelle