У меня есть:
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Есть ли (простой) способ получить общий тип списка?
У меня есть:
List<String> stringList = new ArrayList<String>();
List<Integer> integerList = new ArrayList<Integer>();
Есть ли (простой) способ получить общий тип списка?
Если это действительно поля определенного класса, вы можете получить их с небольшой помощью:
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.
}
}
Вы также можете сделать это для типов параметров и типа возвращаемых методов.
Но если они находятся внутри той же области класса/метода, где вам нужно знать о них, тогда нет смысла их знать, потому что вы уже сами объявили их.
Короткий ответ: нет.
Это, вероятно, дубликат, не может найти подходящего сейчас.
Java использует нечто вроде стирания типа, что означает, что во время выполнения оба объекта эквивалентны. Компилятор знает, что списки содержат целые числа или строки, и как таковые могут поддерживать безопасную среду типа. Эта информация теряется (на основе экземпляра объекта) во время выполнения, а список содержит только "Объекты".
Вы можете немного узнать о классе, какие типы он может быть параметризован, но обычно это просто все, что расширяет "объект", т.е. что угодно. Если вы определяете тип как
class <A extends MyClass> AClass {....}
AClass.class будет содержать только тот факт, что параметр A ограничен MyClass, но более того, нет возможности сказать.
Вы можете сделать то же самое и для параметров метода:
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
Если вам нужно получить общий тип возвращаемого типа, я использовал этот подход, когда мне нужно было найти методы в классе, который возвращал 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
Для нахождения общего типа одного поля:
((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
Универсальный тип коллекции должен иметь значение только в том случае, если в ней есть объекты, верно? Так не проще ли просто сделать:
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());
}
Расширение на ответ Стива К:
/**
* 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 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);
}
}
Во время выполнения нет, вы не можете.
Однако через отражение доступны параметры типа. Попробуйте
for(Field field : this.getDeclaredFields()) {
System.out.println(field.getGenericType())
}
Метод getGenericType()
возвращает объект Type. В этом случае это будет экземпляр ParametrizedType
, который, в свою очередь, имеет методы getRawType()
(который в этом случае будет содержать List.class
) и getActualTypeArguments()
, который вернет массив (в данном случае, из длиной один, содержащий либо String.class
, либо Integer.class
).
Имела ту же проблему, но вместо этого я использовал 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");
}
}
}
Это связано с использованием непроверенных бросков, поэтому делайте это только тогда, когда знаете, что это список и какой тип он может быть.
Как правило, невозможно, поскольку List<String>
и List<Integer>
используют один и тот же класс выполнения.
Возможно, вы сможете задуматься над объявленным типом поля, содержащего список, хотя (если объявленный тип сам не относится к параметру типа, значение которого вы не знаете).
Как говорили другие, единственный правильный ответ - нет, тип был удален.
Если список содержит ненулевое число элементов, вы можете исследовать тип первого элемента (например, с помощью метода getClass). Это не укажет вам общий тип списка, но было бы разумно предположить, что общий тип был некоторым суперклассом типов в списке.
Я бы не поддержал этот подход, но в привязке он может быть полезен.
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();
}
}
Тип стирается, поэтому вы не сможете. См. http://en.wikipedia.org/wiki/Type_erasure и http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure
Используйте Reflection, чтобы получить Field
для них, тогда вы можете просто сделать: field.genericType
, чтобы получить тип, содержащий информацию об общем.