ฉันมี;
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.
}
}
นอกจากนี้คุณยังสามารถทำเช่นนั้นสำหรับประเภทพารามิเตอร์และประเภทวิธี
แต่ถ้าพวกเขาอยู่ในขอบเขตเดียวกันของชั้นเรียน / วิธีการที่คุณจำเป็นต้องรู้เกี่ยวกับพวกเขาแล้วไม่มีจุดรู้ว่าพวกเขาเพราะคุณได้ประกาศตัวเองแล้ว
<?>
(ประเภทไวด์การ์ด) แทน<Class>
(ประเภทคลาส) แทนที่ของคุณ<?>
โดยหรืออะไรก็ตาม<Class>
<E>
คุณสามารถทำเช่นเดียวกันสำหรับพารามิเตอร์วิธีเช่นกัน:
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
คำตอบสั้น ๆ : ไม่
นี่อาจเป็นสิ่งที่ซ้ำกันไม่สามารถหาที่เหมาะสมได้ในขณะนี้
Java ใช้สิ่งที่เรียกว่าการลบประเภทซึ่งหมายความว่า ณ รันไทม์วัตถุทั้งสองจะเทียบเท่า คอมไพเลอร์รู้ว่ารายการมีจำนวนเต็มหรือสตริงและสามารถรักษาสภาพแวดล้อมที่ปลอดภัยประเภท ข้อมูลนี้จะหายไป (บนพื้นฐานอินสแตนซ์ของวัตถุ) ที่รันไทม์และรายการประกอบด้วย 'วัตถุ' เท่านั้น
คุณสามารถหาข้อมูลเล็กน้อยเกี่ยวกับชั้นเรียนสิ่งที่ประเภทมันอาจจะ parametrized โดย แต่ปกตินี้เป็นเพียงสิ่งที่ขยาย "วัตถุ" คืออะไร หากคุณกำหนดประเภทเป็น
class <A extends MyClass> AClass {....}
AClass.class จะประกอบด้วยข้อเท็จจริงที่ว่าพารามิเตอร์ A นั้นล้อมรอบด้วย MyClass แต่ยิ่งไปกว่านั้นไม่มีทางที่จะบอกได้
List<Integer>
และList<String>
; ในกรณีเหล่านี้การลบประเภทจะไม่สามารถใช้ได้
ประเภททั่วไปของคอลเลกชันควรมีความสำคัญถ้ามันมีวัตถุอยู่ในนั้นใช่มั้ย ดังนั้นไม่ใช่เรื่องง่ายที่จะทำ:
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
ไม่มีประเภททั่วไปใน runtime แต่วัตถุภายในที่ runtime รับประกันว่าเป็นประเภทเดียวกับประกาศทั่วไปดังนั้นจึงง่ายพอที่จะทดสอบคลาสของรายการก่อนที่เราจะประมวลผล
อีกสิ่งหนึ่งที่คุณสามารถทำได้คือเพียงดำเนินการรายการเพื่อให้สมาชิกที่เป็นคนประเภทที่ถูกต้องไม่สนใจผู้อื่น
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());
}
Object
s และเมื่อคุณดึงพวกเขาออกจากรายการคุณจะให้พวกเขาจัดการที่เหมาะสมตามประเภทของพวกเขา
สำหรับการค้นหาประเภททั่วไปของหนึ่งฟิลด์:
((Class)((ParameterizedType)field.getGenericType()).getActualTypeArguments()[0]).getSimpleName()
หากคุณต้องการรับประเภททั่วไปของประเภทที่ส่งคืนฉันใช้วิธีนี้เมื่อฉันต้องการค้นหาวิธีการในชั้นเรียนที่คืนค่า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
ขยายคำตอบของ 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);
}
}
ณ รันไทม์ไม่คุณทำไม่ได้
แต่ผ่านการสะท้อนพารามิเตอร์ชนิดมีความสามารถเข้าถึงได้ ลอง
for(Field field : this.getDeclaredFields()) {
System.out.println(field.getGenericType())
}
วิธีการgetGenericType()
ส่งกลับวัตถุประเภท ในกรณีนี้มันจะเป็นตัวอย่างของParametrizedType
ซึ่งจะมีวิธีการgetRawType()
(ซึ่งจะมีList.class
ในกรณีนี้) และgetActualTypeArguments()
ซึ่งจะส่งกลับอาร์เรย์ (ในกรณีนี้ของความยาวหนึ่งที่มีอย่างใดอย่างหนึ่งString.class
หรือInteger.class
)
method.getGenericParameterTypes()
เพื่อรับชนิดพารามิเตอร์ที่ประกาศไว้ของเมธอด
มีปัญหาเดียวกัน แต่ฉันใช้อินสแตนซ์ของแทน ทำแบบนี้:
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 เป็นตัวอย่าง) ที่จะไม่บอกคุณประเภททั่วไปของรายการ แต่ก็มีเหตุผลที่จะถือว่าประเภททั่วไปเป็น superclass บางประเภทในรายการ
ฉันจะไม่สนับสนุนวิธีการนี้ แต่ในทางกลับกันมันอาจมีประโยชน์
List<Integer>
และList<String>
; ในกรณีเหล่านี้การลบประเภทจะไม่สามารถใช้ได้
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
มันไม่สามารถพบได้
ประเภทถูกลบดังนั้นคุณจะไม่สามารถ ดูhttp://en.wikipedia.org/wiki/Type_erasureและhttp://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure
List<Integer>
และList<String>
; ในกรณีเหล่านี้การลบประเภทจะไม่สามารถใช้ได้
ใช้ Reflection เพื่อรับสิ่งField
เหล่านี้จากนั้นคุณสามารถทำได้field.genericType
เพื่อรับชนิดที่มีข้อมูลเกี่ยวกับ generic เช่นกัน