Mam;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Czy istnieje (łatwy) sposób na uzyskanie ogólnego typu listy?
Mam;
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Czy istnieje (łatwy) sposób na uzyskanie ogólnego typu listy?
Odpowiedzi:
Jeśli faktycznie są to pola pewnej klasy, możesz je uzyskać z niewielką pomocą refleksji:
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.
}
}
Możesz to również zrobić dla typów parametrów i zwracanych metod.
Ale jeśli znajdują się w tym samym zakresie klasy / metody, w którym musisz o nich wiedzieć, to nie ma sensu ich znać, ponieważ już je sam zadeklarowałeś.
<?>(typ wieloznaczny) zamiast <Class>(typ klasy). Wymień <?>By <Class>lub cokolwiek <E>.
Możesz zrobić to samo dla parametrów metody:
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
Krótka odpowiedź: nie.
To prawdopodobnie duplikat, nie można teraz znaleźć odpowiedniego.
Java używa czegoś o nazwie kasowanie typu, co oznacza, że w czasie wykonywania oba obiekty są równoważne. Kompilator wie, że listy zawierają liczby całkowite lub ciągi i jako takie mogą utrzymywać bezpieczne środowisko typu. Informacje te są tracone (na podstawie instancji obiektu) w czasie wykonywania, a lista zawiera tylko „Obiekty”.
Możesz dowiedzieć się trochę o klasie, jakie typy może być parametryzowane, ale zwykle jest to po prostu wszystko, co rozszerza „Obiekt”, tj. Cokolwiek. Jeśli zdefiniujesz typ jako
class <A extends MyClass> AClass {....}
AClass.class będzie zawierał tylko fakt, że parametr A jest ograniczony przez MyClass, ale co więcej, nie można tego stwierdzić.
List<Integer>i List<String>; w takich przypadkach usunięcie typu nie ma zastosowania.
Ogólny typ kolekcji powinien mieć znaczenie tylko wtedy, gdy rzeczywiście zawiera w sobie obiekty, prawda? Czy nie jest łatwiej zrobić:
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
W środowisku wykonawczym nie ma czegoś takiego jak rodzaj ogólny, ale na pewno obiekty wewnątrz środowiska wykonawczego są tego samego typu co deklarowany rodzaj ogólny, więc wystarczy przetestować klasę elementu przed przetworzeniem.
Inną rzeczą, którą możesz zrobić, to po prostu przetworzyć listę, aby uzyskać członków odpowiedniego typu, ignorując innych (lub przetwarzając ich inaczej).
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());
To da ci listę wszystkich przedmiotów, których klasy były podklasami, Numberktóre możesz następnie przetwarzać według potrzeb. Pozostałe elementy zostały odfiltrowane na inne listy. Ponieważ znajdują się na mapie, możesz je przetwarzać zgodnie z potrzebami lub ignorować.
Jeśli chcesz całkowicie zignorować elementy innych klas, staje się to znacznie prostsze:
List<Number> numbers = myCollection.stream()
.filter(Number.class::isInstance)
.map(Number.class::cast)
.collect(Collectors.toList());
Możesz nawet utworzyć metodę narzędzia, aby upewnić się, że lista zawiera TYLKO te elementy, które pasują do określonej klasy:
public <V> List<V> getTypeSafeItemList(Collection<Object> input, Class<V> cls) {
return input.stream()
.filter(cls::isInstance)
.map(cls::cast)
.collect(Collectors.toList());
}
Objects, a kiedy wyciągniesz je z listy, podasz je odpowiedniemu programowi obsługi na podstawie ich typu.
Aby znaleźć ogólny typ jednego pola:
((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
Jeśli chcesz uzyskać ogólny typ zwracanego typu, zastosowałem to podejście, gdy potrzebowałem znaleźć metody w klasie, która zwróciła a, Collectiona następnie uzyskać dostęp do ich ogólnych typów:
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]);
}
}
}
}
}
}
To daje:
Typ ogólny to klasa java.lang.String
Rozwijając odpowiedź Steve'a 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);
}
}
W czasie wykonywania nie możesz.
Jednak poprzez odbicie parametry typu są dostępne. Próbować
for(Field field : this.getDeclaredFields()) {
System.out.println(field.getGenericType())
}
Metoda getGenericType()zwraca obiekt typu. W tym przypadku będzie to instancja ParametrizedType, która z kolei ma metody getRawType()(które List.classw tym przypadku będą zawierać ) i getActualTypeArguments()która zwróci tablicę (w tym przypadku o długości 1, zawierającą albo String.classlub Integer.class).
method.getGenericParameterTypes()aby uzyskać zadeklarowane typy parametrów metody.
Miałem ten sam problem, ale zamiast tego użyłem instanceof. Czy to w ten sposób:
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");
}
}
}
Wymaga to użycia niezaznaczonych rzutów, więc rób to tylko wtedy, gdy wiesz, że jest to lista i jaki może być typ.
Jak powiedzieli inni, jedyną prawidłową odpowiedzią jest nie, typ został usunięty.
Jeśli lista zawiera niezerową liczbę elementów, możesz zbadać typ pierwszego elementu (na przykład używając jego metody getClass). To nie powie ci o ogólnym typie listy, ale rozsądnie byłoby założyć, że typem ogólnym była jakaś nadklasa typów na liście.
Nie opowiadałbym się za tym podejściem, ale w przypadku powiązania może być przydatne.
List<Integer>i List<String>; w takich przypadkach usunięcie typu nie ma zastosowania.
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();
}
}
getFieldGenericTypefieldGenericTypenie można znaleźć
Typ zostanie usunięty, więc nie będziesz mógł tego zrobić. Zobacz http://en.wikipedia.org/wiki/Type_erasure i http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure
List<Integer>i List<String>; w takich przypadkach usunięcie typu nie ma zastosowania.
Użyj Reflection, aby uzyskać Fielddla nich, a następnie możesz po prostu: field.genericTypeuzyskać typ, który zawiera również informacje o ogólnych.