Untuk dapat secara terprogram memeriksa objek Daftar dan melihat jenis generiknya. Suatu metode mungkin ingin menyisipkan objek berdasarkan tipe generik dari koleksi. Ini dimungkinkan dalam bahasa yang mengimplementasikan generik pada saat runtime alih-alih waktu kompilasi.
Steve Kuo
4
Kanan - tentang satu-satunya cara untuk memungkinkan deteksi runtime adalah dengan subklasifikasi: Anda BISA benar-benar memperluas tipe generik dan kemudian menggunakan deklarasi tipe temukan refleksi yang digunakan subtipe. Ini sedikit refleksi, tetapi mungkin. Sayangnya tidak ada cara mudah untuk menegakkan bahwa seseorang harus menggunakan sub-kelas generik.
StaxMan
1
Tentunya stringList berisi string dan integerList integer? Mengapa membuatnya lebih rumit?
Ben Thurley
Jawaban:
408
Jika itu benar-benar bidang kelas tertentu, maka Anda bisa mendapatkannya dengan sedikit bantuan refleksi:
Anda juga dapat melakukan itu untuk tipe parameter dan mengembalikan jenis metode.
Tetapi jika mereka berada dalam ruang lingkup yang sama dari kelas / metode di mana Anda perlu tahu tentang mereka, maka tidak ada gunanya mengenal mereka, karena Anda sudah menyatakannya sendiri.
@Oleg Variabel Anda menggunakan <?>(tipe wildcard) alih-alih <Class>(tipe kelas). Ganti <?>dengan <Class>atau apa pun <E>.
BalusC
19
Anda dapat melakukan hal yang sama untuk parameter metode juga:
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
Dalam method.getGenericParameterTypes (); apa itu metode?
Neo Ravi
18
Jawaban singkat: tidak.
Ini mungkin duplikat, tidak dapat menemukan yang tepat sekarang.
Java menggunakan sesuatu yang disebut tipe erasure, yang berarti pada saat runtime kedua objek sama. Kompiler tahu daftar berisi bilangan bulat atau string, dan dengan demikian dapat mempertahankan tipe lingkungan yang aman. Informasi ini hilang (berdasarkan instance objek) saat runtime, dan daftar hanya berisi 'Objek'.
Anda BISA mencari tahu sedikit tentang kelas, apa jenisnya dapat ditentukan oleh, tetapi biasanya ini hanya apa saja yang memperluas "Objek", yaitu apa saja. Jika Anda mendefinisikan tipe sebagai
class<A extendsMyClass>AClass{....}
AClass.class hanya akan berisi fakta bahwa parameter A dibatasi oleh MyClass, tetapi lebih dari itu, tidak ada cara untuk mengatakannya.
Itu akan benar jika kelas konkret generik tidak ditentukan; dalam contohnya, dia secara eksplisit menyatakan daftar sebagai List<Integer>dan List<String>; dalam kasus tersebut, tipe penghapusan tidak berlaku.
Haroldo_OK
13
Jenis generik koleksi seharusnya hanya masalah jika itu benar-benar memiliki objek di dalamnya, bukan? Jadi bukankah lebih mudah melakukannya:
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
Tidak ada yang namanya tipe generik dalam runtime, tetapi objek di dalam saat runtime dijamin menjadi tipe yang sama dengan generik yang dideklarasikan, jadi cukup mudah hanya untuk menguji kelas item sebelum kita memprosesnya.
Hal lain yang dapat Anda lakukan adalah hanya memproses daftar untuk mendapatkan anggota yang tipe yang tepat, mengabaikan orang lain (atau memprosesnya secara berbeda).
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());
Ini akan memberi Anda daftar semua item yang kelasnya adalah subclass Numberyang kemudian dapat Anda proses sesuai kebutuhan. Sisa item disaring ke daftar lain. Karena mereka ada di peta, Anda dapat memprosesnya sesuai keinginan, atau mengabaikannya.
Jika Anda ingin mengabaikan item dari kelas lain sekaligus, itu menjadi jauh lebih sederhana:
Dan jika Anda memiliki koleksi kosong? Atau jika Anda memiliki Koleksi <Object> dengan Integer dan String?
Falci
Kontrak untuk kelas hanya bisa mengharuskan daftar objek final diteruskan, dan bahwa pemanggilan metode akan mengembalikan nol jika daftar tersebut nol, kosong, atau jika jenis daftar tidak valid.
ggb667
1
Dalam kasus koleksi kosong, aman untuk menetapkan ke semua jenis daftar saat runtime, karena tidak ada apa pun di dalamnya, dan setelah penghapusan tipe terjadi, daftar adalah daftar adalah daftar. Karena itu, saya tidak dapat memikirkan contoh di mana jenis koleksi kosong akan penting.
Steve K
Yah itu "bisa jadi masalah" jika Anda memegang referensi di utas dan memprosesnya setelah objek mulai muncul dalam skenario konsumen produsen. Jika daftar memiliki jenis objek yang salah, hal-hal buruk dapat terjadi. Saya kira untuk mencegah Anda harus menghentikan pemrosesan dengan tidur sampai ada setidaknya satu item dalam daftar sebelum melanjutkan.
ggb667
Jika Anda memiliki skenario produsen / konsumen seperti itu, rencanakan daftar untuk memiliki jenis generik tertentu tanpa yakin apa yang dapat dimasukkan ke dalam daftar adalah desain yang buruk. Alih-alih Anda akan mendeklarasikannya sebagai koleksi Objectdan ketika Anda menariknya keluar dari daftar, Anda akan memberikannya kepada penangan yang tepat berdasarkan jenisnya.
Jika Anda perlu mendapatkan tipe generik dari tipe yang dikembalikan, saya menggunakan pendekatan ini ketika saya perlu menemukan metode di kelas yang mengembalikan a Collectiondan kemudian mengakses tipe generiknya:
/**
* 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);}}
Masalah yang sama dengan jawaban Steve K: Bagaimana jika daftar itu kosong? Bagaimana jika daftar ini bertipe <Object> yang berisi String dan Integer? Kode Anda berarti bahwa Anda hanya dapat menambahkan lebih banyak String jika item pertama dalam daftar adalah string, dan tidak ada bilangan bulat sama sekali ...
subrunner
Jika daftar kosong Anda kurang beruntung. Jika daftar itu adalah Objek dan sebenarnya berisi Obyek Anda OK, tetapi jika memiliki campuran hal Anda kurang beruntung. Penghapusan berarti ini sebaik yang bisa Anda lakukan. Penghapusan kurang dalam pendapat saya. Konsekuensi dari itu sekaligus kurang dipahami dan tidak cukup untuk menangani kasus penggunaan yang menarik dan diinginkan yang menjadi perhatian khusus. Tidak ada yang mencegah membuat kelas yang bisa ditanyakan jenis apa yang diperlukan, tetapi itu bukan bagaimana kelas built in bekerja. Koleksi seharusnya tahu apa yang dikandungnya, tapi sayang ...
ggb667
4
Saat runtime, tidak, Anda tidak bisa.
Namun melalui refleksi parameter tipe dapat diakses. Mencoba
for(Field field :this.getDeclaredFields()){System.out.println(field.getGenericType())}
Metode getGenericType()mengembalikan objek Type. Dalam hal ini, ini akan menjadi turunan dari ParametrizedType, yang pada gilirannya memiliki metode getRawType()(yang akan berisi List.class, dalam kasus ini) dan getActualTypeArguments(), yang akan mengembalikan array (dalam hal ini, panjang satu, berisi salah satu String.classatau Integer.class).
Punya masalah yang sama, tapi saya menggunakan instanceof. Apakah dengan cara ini:
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");}}}
Ini melibatkan penggunaan gips yang tidak dicentang sehingga hanya melakukan ini ketika Anda tahu itu adalah daftar, dan apa jenisnya.
Umumnya tidak mungkin, karena List<String>danList<Integer> berbagi kelas runtime yang sama.
Anda mungkin dapat merefleksikan tipe yang dideklarasikan dari field yang menyimpan daftar, meskipun (jika tipe yang dideklarasikan itu sendiri tidak merujuk pada parameter tipe yang nilainya tidak Anda ketahui).
Seperti yang dikatakan orang lain, satu-satunya jawaban yang benar adalah tidak, tipe telah dihapus.
Jika daftar memiliki jumlah elemen yang tidak nol, Anda bisa menyelidiki tipe elemen pertama (misalnya menggunakan metode getClass). Itu tidak akan memberi tahu Anda tipe generik dari daftar, tetapi akan masuk akal untuk menganggap bahwa tipe generik adalah beberapa superclass dari tipe dalam daftar.
Saya tidak akan menganjurkan pendekatan itu, tetapi mungkin itu berguna.
Itu akan benar jika kelas konkret generik tidak ditentukan; dalam contohnya, dia secara eksplisit menyatakan daftar sebagai List<Integer>dan List<String>; dalam kasus tersebut, tipe penghapusan tidak berlaku.
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();}}
Itu akan benar jika kelas konkret generik tidak ditentukan; dalam contohnya, dia secara eksplisit menyatakan daftar sebagai List<Integer>dan List<String>; dalam kasus tersebut, tipe penghapusan tidak berlaku.
Haroldo_OK
0
Gunakan Reflection untuk mendapatkan Fieldini maka Anda bisa melakukan: field.genericTypeuntuk mendapatkan jenis yang berisi informasi tentang generik juga.
Jawaban:
Jika itu benar-benar bidang kelas tertentu, maka Anda bisa mendapatkannya dengan sedikit bantuan refleksi:
Anda juga dapat melakukan itu untuk tipe parameter dan mengembalikan jenis metode.
Tetapi jika mereka berada dalam ruang lingkup yang sama dari kelas / metode di mana Anda perlu tahu tentang mereka, maka tidak ada gunanya mengenal mereka, karena Anda sudah menyatakannya sendiri.
sumber
<?>
(tipe wildcard) alih-alih<Class>
(tipe kelas). Ganti<?>
dengan<Class>
atau apa pun<E>
.Anda dapat melakukan hal yang sama untuk parameter metode juga:
sumber
Jawaban singkat: tidak.
Ini mungkin duplikat, tidak dapat menemukan yang tepat sekarang.
Java menggunakan sesuatu yang disebut tipe erasure, yang berarti pada saat runtime kedua objek sama. Kompiler tahu daftar berisi bilangan bulat atau string, dan dengan demikian dapat mempertahankan tipe lingkungan yang aman. Informasi ini hilang (berdasarkan instance objek) saat runtime, dan daftar hanya berisi 'Objek'.
Anda BISA mencari tahu sedikit tentang kelas, apa jenisnya dapat ditentukan oleh, tetapi biasanya ini hanya apa saja yang memperluas "Objek", yaitu apa saja. Jika Anda mendefinisikan tipe sebagai
AClass.class hanya akan berisi fakta bahwa parameter A dibatasi oleh MyClass, tetapi lebih dari itu, tidak ada cara untuk mengatakannya.
sumber
List<Integer>
danList<String>
; dalam kasus tersebut, tipe penghapusan tidak berlaku.Jenis generik koleksi seharusnya hanya masalah jika itu benar-benar memiliki objek di dalamnya, bukan? Jadi bukankah lebih mudah melakukannya:
Tidak ada yang namanya tipe generik dalam runtime, tetapi objek di dalam saat runtime dijamin menjadi tipe yang sama dengan generik yang dideklarasikan, jadi cukup mudah hanya untuk menguji kelas item sebelum kita memprosesnya.
Hal lain yang dapat Anda lakukan adalah hanya memproses daftar untuk mendapatkan anggota yang tipe yang tepat, mengabaikan orang lain (atau memprosesnya secara berbeda).
Ini akan memberi Anda daftar semua item yang kelasnya adalah subclass
Number
yang kemudian dapat Anda proses sesuai kebutuhan. Sisa item disaring ke daftar lain. Karena mereka ada di peta, Anda dapat memprosesnya sesuai keinginan, atau mengabaikannya.Jika Anda ingin mengabaikan item dari kelas lain sekaligus, itu menjadi jauh lebih sederhana:
Anda bahkan dapat membuat metode utilitas untuk memastikan bahwa daftar berisi HANYA item yang cocok dengan kelas tertentu:
sumber
Object
dan ketika Anda menariknya keluar dari daftar, Anda akan memberikannya kepada penangan yang tepat berdasarkan jenisnya.Untuk menemukan tipe generik dari satu bidang:
sumber
Jika Anda perlu mendapatkan tipe generik dari tipe yang dikembalikan, saya menggunakan pendekatan ini ketika saya perlu menemukan metode di kelas yang mengembalikan a
Collection
dan kemudian mengakses tipe generiknya:Output ini:
sumber
Memperluas jawaban Steve K:
sumber
Saat runtime, tidak, Anda tidak bisa.
Namun melalui refleksi parameter tipe dapat diakses. Mencoba
Metode
getGenericType()
mengembalikan objek Type. Dalam hal ini, ini akan menjadi turunan dariParametrizedType
, yang pada gilirannya memiliki metodegetRawType()
(yang akan berisiList.class
, dalam kasus ini) dangetActualTypeArguments()
, yang akan mengembalikan array (dalam hal ini, panjang satu, berisi salah satuString.class
atauInteger.class
).sumber
method.getGenericParameterTypes()
untuk mendapatkan tipe parameter metode yang dideklarasikan.Punya masalah yang sama, tapi saya menggunakan instanceof. Apakah dengan cara ini:
Ini melibatkan penggunaan gips yang tidak dicentang sehingga hanya melakukan ini ketika Anda tahu itu adalah daftar, dan apa jenisnya.
sumber
Umumnya tidak mungkin, karena
List<String>
danList<Integer>
berbagi kelas runtime yang sama.Anda mungkin dapat merefleksikan tipe yang dideklarasikan dari field yang menyimpan daftar, meskipun (jika tipe yang dideklarasikan itu sendiri tidak merujuk pada parameter tipe yang nilainya tidak Anda ketahui).
sumber
Seperti yang dikatakan orang lain, satu-satunya jawaban yang benar adalah tidak, tipe telah dihapus.
Jika daftar memiliki jumlah elemen yang tidak nol, Anda bisa menyelidiki tipe elemen pertama (misalnya menggunakan metode getClass). Itu tidak akan memberi tahu Anda tipe generik dari daftar, tetapi akan masuk akal untuk menganggap bahwa tipe generik adalah beberapa superclass dari tipe dalam daftar.
Saya tidak akan menganjurkan pendekatan itu, tetapi mungkin itu berguna.
sumber
List<Integer>
danList<String>
; dalam kasus tersebut, tipe penghapusan tidak berlaku.sumber
getFieldGenericTypefieldGenericType
itu tidak dapat ditemukanJenis ini dihapus sehingga Anda tidak akan bisa. Lihat http://en.wikipedia.org/wiki/Type_erasure dan http://en.wikipedia.org/wiki/Generics_in_Java#Type_erasure
sumber
List<Integer>
danList<String>
; dalam kasus tersebut, tipe penghapusan tidak berlaku.Gunakan Reflection untuk mendapatkan
Field
ini maka Anda bisa melakukan:field.genericType
untuk mendapatkan jenis yang berisi informasi tentang generik juga.sumber