Io ho;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Esiste un modo (semplice) per recuperare il tipo generico dell'elenco?
Io ho;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Esiste un modo (semplice) per recuperare il tipo generico dell'elenco?
Risposte:
Se quelli sono effettivamente campi di una certa classe, puoi ottenerli con un piccolo aiuto di riflessione:
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.
}
}
Puoi anche farlo per tipi di parametri e tipi di metodi di restituzione.
Ma se sono all'interno dello stesso ambito della classe / metodo in cui è necessario conoscerli, allora non ha senso conoscerli, perché li hai già dichiarati tu stesso.
<?>
(tipo jolly) anziché <Class>
(tipo di classe). Sostituisci il tuo <?>
con <Class>
o altro <E>
.
Puoi fare lo stesso anche per i parametri del metodo:
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
Risposta breve: no.
Questo è probabilmente un duplicato, non riesco a trovarne uno appropriato in questo momento.
Java utilizza una cosiddetta cancellazione del tipo, il che significa che in fase di esecuzione entrambi gli oggetti sono equivalenti. Il compilatore sa che le liste contengono numeri interi o stringhe e come tali possono mantenere un ambiente sicuro di tipo. Questa informazione viene persa (in base all'istanza di un oggetto) in fase di esecuzione e l'elenco contiene solo "Oggetti".
PUOI scoprire qualcosa sulla classe, su quali tipi potrebbe essere parametrizzata, ma normalmente questo è tutto ciò che estende "Object", cioè qualsiasi cosa. Se si definisce un tipo come
class <A extends MyClass> AClass {....}
AClass.class conterrà solo il fatto che il parametro A è limitato da MyClass, ma soprattutto non c'è modo di dirlo.
List<Integer>
e List<String>
; in questi casi, la cancellazione del tipo non si applica.
Il tipo generico di una raccolta dovrebbe avere importanza solo se contiene effettivamente degli oggetti, giusto? Quindi non è più semplice fare semplicemente:
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
Non esiste un tipo generico in runtime, ma gli oggetti all'interno in runtime sono garantiti dello stesso tipo del generico dichiarato, quindi è abbastanza semplice testare la classe dell'articolo prima di elaborarlo.
Un'altra cosa che puoi fare è semplicemente elaborare l'elenco per ottenere membri del tipo giusto, ignorandone altri (o elaborandoli in modo diverso).
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());
Questo ti darà un elenco di tutti gli elementi le cui classi erano sottoclassi di Number
cui puoi quindi elaborare secondo le tue necessità. Il resto degli elementi è stato filtrato in altri elenchi. Poiché si trovano nella mappa, è possibile elaborarli come desiderato o ignorarli.
Se vuoi ignorare del tutto gli elementi di altre classi, diventa molto più semplice:
List<Number> numbers = myCollection.stream()
.filter(Number.class::isInstance)
.map(Number.class::cast)
.collect(Collectors.toList());
È anche possibile creare un metodo di utilità per assicurare che un elenco contenga SOLO quegli elementi che corrispondono a una classe specifica:
public <V> List<V> getTypeSafeItemList(Collection<Object> input, Class<V> cls) {
return input.stream()
.filter(cls::isInstance)
.map(cls::cast)
.collect(Collectors.toList());
}
Object
s e quando li tirerai fuori dall'elenco, li daresti a un gestore appropriato in base al loro tipo.
Per trovare un tipo generico di un campo:
((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
Se è necessario ottenere il tipo generico di un tipo restituito, ho utilizzato questo approccio quando avevo bisogno di trovare metodi in una classe che ha restituito un Collection
e quindi accedere ai loro tipi generici:
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]);
}
}
}
}
}
}
Questo produce:
Il tipo generico è la classe java.lang.String
Espandendo la risposta di 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);
}
}
In fase di esecuzione, no, non puoi.
Tuttavia, tramite la riflessione, i parametri del tipo sono accessibili. Provare
for(Field field : this.getDeclaredFields()) {
System.out.println(field.getGenericType())
}
Il metodo getGenericType()
restituisce un oggetto Type. In questo caso, sarà un'istanza di ParametrizedType
, che a sua volta ha metodi getRawType()
(che conterrà List.class
, in questo caso) e getActualTypeArguments()
, che restituirà un array (in questo caso, di lunghezza uno, contenente String.class
o Integer.class
).
method.getGenericParameterTypes()
per ottenere i tipi dichiarati dei parametri di un metodo.
Ho avuto lo stesso problema, ma ho usato invece instanceof. Ha fatto così:
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");
}
}
}
Ciò implica l'uso di cast non controllati, quindi fallo solo quando sai che è un elenco e che tipo può essere.
Generalmente impossibile, perché List<String>
e List<Integer>
condividere la stessa classe di runtime.
Tuttavia, potresti essere in grado di riflettere sul tipo dichiarato del campo che contiene l'elenco (se il tipo dichiarato non si riferisce a un parametro di tipo di cui non conosci il valore).
Come altri hanno già detto, l'unica risposta corretta è no, il tipo è stato cancellato.
Se l'elenco ha un numero di elementi diverso da zero, è possibile esaminare il tipo del primo elemento (utilizzando il metodo getClass, ad esempio). Ciò non ti dirà il tipo generico dell'elenco, ma sarebbe ragionevole supporre che il tipo generico fosse una superclasse dei tipi nell'elenco.
Non difenderei l'approccio, ma in un certo senso potrebbe essere utile.
List<Integer>
e List<String>
; in questi casi, la cancellazione del tipo non si applica.
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();
}
}
getFieldGenericTypefieldGenericType
non può essere trovato
Il tipo viene cancellato, quindi non sarà possibile. Vedi http://en.wikipedia.org/wiki/Type_erasure e http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure
List<Integer>
e List<String>
; in questi casi, la cancellazione del tipo non si applica.
Usa Reflection per ottenere Field
questi, quindi puoi semplicemente fare: field.genericType
per ottenere il tipo che contiene anche le informazioni su generico.