Bagaimana saya mendapatkan instance kelas dari tipe T generik?
700
Saya memiliki kelas generik Foo<T>,. Dalam suatu metode Foo, saya ingin mendapatkan instance kelas dari tipe T, tapi saya tidak bisa menelepon T.class.
Apa cara yang disukai untuk menggunakannya T.class?
import com.fasterxml.jackson.core.type.TypeReference; new TypeReference<T>(){}
Bogdan Shulga
Jawaban:
567
Jawaban singkatnya adalah, bahwa tidak ada cara untuk mengetahui tipe runtime dari parameter tipe generik di Jawa. Saya sarankan membaca bab tentang penghapusan tipe dalam Tutorial Java untuk lebih jelasnya.
Solusi populer untuk ini adalah untuk melewatkan Classparameter tipe ke konstruktor dari tipe generik, misalnya
classFoo<T>{finalClass<T> typeParameterClass;publicFoo(Class<T> typeParameterClass){this.typeParameterClass = typeParameterClass;}publicvoid bar(){// you can access the typeParameterClass here and do whatever you like}}
Jawaban ini memang memberikan solusi yang valid tetapi tidak akurat untuk mengatakan tidak ada cara untuk menemukan tipe generik saat runtime. Ternyata tipe erasure jauh lebih kompleks daripada erasure blanket. Jawaban saya menunjukkan kepada Anda cara mendapatkan tipe generik kelas.
Ben Thurley
3
Tren @BenThurley Rapi, tapi sejauh yang saya bisa lihat itu hanya berfungsi jika ada supertype generik untuk digunakan. Dalam contoh saya, Anda tidak dapat mengambil jenis T di Foo <T>.
Zsolt Török
@webjockey Tidak, tidak seharusnya. Menetapkan typeParameterClasstanpa penugasan default di konstruktor baik-baik saja. Tidak perlu mengaturnya untuk yang kedua kalinya.
Adowrath
Ini adalah solusi pertama yang muncul di pikiran, tetapi kadang-kadang bukan Anda yang akan membuat / memulai objek. Jadi Anda tidak akan dapat menggunakan konstruktor. Untuk misalnya saat mengambil entitas JPA dari database.
Paramvir Singh Karwal
233
Saya mencari cara untuk melakukan ini sendiri tanpa menambahkan ketergantungan ekstra ke classpath. Setelah beberapa penyelidikan saya menemukan bahwa itu adalah mungkin selama Anda memiliki supertype generik. Ini tidak masalah bagi saya karena saya bekerja dengan layer DAO dengan supertype layer generik. Jika ini cocok dengan skenario Anda, maka itu adalah pendekatan IMHO yang paling rapi.
Sebagian besar kasus penggunaan generik yang saya temui memiliki semacam supertipe generik misalnya List<T>untuk ArrayList<T>atau GenericDAO<T>untukDAO<T> , dll.
Proyek saya menggunakan Spring yang bahkan lebih baik karena Spring memiliki metode utilitas praktis untuk menemukan tipe. Ini adalah pendekatan terbaik bagi saya karena terlihat rapi. Saya kira jika Anda tidak menggunakan Spring Anda bisa menulis metode utilitas Anda sendiri.
import org.springframework.core.GenericTypeResolver;publicabstractclassAbstractHibernateDao<T extendsDomainObject>implementsDataAccessObject<T>{@AutowiredprivateSessionFactory sessionFactory;privatefinalClass<T> genericType;privatefinalString RECORD_COUNT_HQL;privatefinalString FIND_ALL_HQL;@SuppressWarnings("unchecked")publicAbstractHibernateDao(){this.genericType =(Class<T>)GenericTypeResolver.resolveTypeArgument(getClass(),AbstractHibernateDao.class);this.RECORD_COUNT_HQL ="select count(*) from "+this.genericType.getName();this.FIND_ALL_HQL ="from "+this.genericType.getName()+" t ";}
silahkan menjelaskan makna dari resolveTypeArgument argumen
gstackoverflow
getClass () adalah metode java.lang.Object yang akan mengembalikan kelas objek spesifik saat runtime, ini adalah objek yang Anda ingin atur jenisnya. AbstractHibernateDao.class hanyalah nama kelas dasar atau superclass dari hierarki kelas tipe generik. Pernyataan impor disertakan sehingga Anda harus dapat dengan mudah menemukan dokumen dan memeriksa diri Anda sendiri. Ini adalah halaman docs.spring.io/spring/docs/current/javadoc-api/org/…
Ben Thurley
Apa solusi pegas dengan versi 4.3.6 dan lebih tinggi. Itu tidak bekerja dengan pegas 4.3.6.
@ AlikElzin-kilaka akan diinisialisasi dalam konstruktor menggunakan kelas Spring GenericTypeResolver.
Ben Thurley
103
Namun ada celah kecil: jika Anda mendefinisikan Fookelas Anda sebagai abstrak. Itu berarti Anda harus membuat instantiate kelas Anda sebagai:
Foo<MyType> myFoo =newFoo<MyType>(){};
(Perhatikan kurung ganda di bagian akhir.)
Sekarang Anda dapat mengambil jenis Tsaat runtime:
Type mySuperclass = myFoo.getClass().getGenericSuperclass();Type tType =((ParameterizedType)mySuperclass).getActualTypeArguments()[0];
Namun perlu dicatat bahwa mySuperclassitu harus menjadi superkelas dari definisi kelas sebenarnya mendefinisikan tipe final untuk T.
Ini juga tidak terlalu elegan, tetapi Anda harus memutuskan apakah Anda lebih suka new Foo<MyType>(){}atau new Foo<MyType>(MyType.class);dalam kode Anda.
Sebagai contoh:
import java.lang.reflect.ParameterizedType;import java.lang.reflect.Type;import java.util.ArrayDeque;import java.util.Deque;import java.util.NoSuchElementException;/**
* Captures and silently ignores stack exceptions upon popping.
*/publicabstractclassSilentStack<E>extendsArrayDeque<E>{public E pop(){try{returnsuper.pop();}catch(NoSuchElementException nsee ){return create();}}public E create(){try{Type sooper = getClass().getGenericSuperclass();Type t =((ParameterizedType)sooper).getActualTypeArguments()[0];return(E)(Class.forName( t.toString()).newInstance());}catch(Exception e ){returnnull;}}}
Kemudian:
publicclassMain{// Note the braces...privateDeque<String> stack =newSilentStack<String>(){};publicstaticvoid main(String args[]){// Returns a new instance of String.String s = stack.pop();System.out.printf("s = '%s'\n", s );}}
Ini dengan mudah jawaban terbaik di sini! Juga, untuk apa nilainya, ini adalah strategi yang digunakan Google Guice untuk mengikat kelas denganTypeLiteral
ATG
14
Perhatikan bahwa setiap kali metode konstruksi objek ini digunakan, kelas anonim baru dibuat. Dengan kata lain, dua objek adan bdibuat dengan cara ini akan memperluas kelas yang sama tetapi tidak memiliki kelas instance yang identik. a.getClass() != b.getClass()
Martin Serrano
3
Ada satu skenario di mana ini tidak berhasil. Jika Foo harus mengimplementasikan antarmuka, seperti Serializable, dari kelas anonim tidak akan Serializable kecuali kelas instantiating adalah. Saya mencoba untuk mengatasinya dengan membuat kelas pabrik serializable yang menciptakan kelas anonim yang berasal dari Foo, tetapi kemudian, untuk beberapa alasan, getActualTypeArguments mengembalikan tipe generik alih-alih kelas aktual. Misalnya: (FooFactory <MyType> ()) baru. CreateFoo ()
Lior Chaga
38
Pendekatan / solusi / solusi standar adalah dengan menambahkan classobjek ke konstruktor, seperti:
publicclassFoo<T>{privateClass<T> type;publicFoo(Class<T> type){this.type = type;}publicClass<T> getType(){return type;}public T newInstance(){return type.newInstance();}}
Tapi sepertinya tidak bisa @autowired dalam penggunaan aktual, cara apa pun untuk mengatasinya?
Alfred Huang
@AlfredHuang Pekerjaan di sekitar adalah membuat kacang untuk kelas yang melakukan ini dan tidak bergantung pada autowiring.
Calebj
20
Bayangkan Anda memiliki superclass abstrak yang generik:
publicabstractclassFoo<?extends T>{}
Dan kemudian Anda memiliki kelas kedua yang memanjang Foo dengan Bar generik yang memanjang T:
publicclassSecondextendsFoo<Bar>{}
Anda bisa mendapatkan kelas Bar.classdi kelas Foo dengan memilih Type(dari jawaban bert bruynooghe) dan menyimpulkannya menggunakan Classinstance:
Type mySuperclass = myFoo.getClass().getGenericSuperclass();Type tType =((ParameterizedType)mySuperclass).getActualTypeArguments()[0];//Parse it as StringString className = tType.toString().split(" ")[1];Class clazz =Class.forName(className);
Anda harus mencatat bahwa operasi ini tidak ideal, sehingga merupakan ide bagus untuk men-cache nilai yang dihitung untuk menghindari beberapa perhitungan. Salah satu kegunaan khas adalah dalam implementasi DAO generik.
@SuppressWarnings("unchecked")privateClass<T> getGenericTypeClass(){try{String className =((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();Class<?> clazz =Class.forName(className);return(Class<T>) clazz;}catch(Exception e){thrownewIllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");}}
CATATAN:
Dapat digunakan hanya sebagai superclass
Harus diperluas dengan kelas yang diketik ( Child extends Generic<Integer>)
ATAU
Harus dibuat sebagai implementasi anonim ( new Generic<Integer>() {};)
Rute yang lebih baik daripada Kelas yang disarankan orang lain adalah lewat di objek yang dapat melakukan apa yang akan Anda lakukan dengan Kelas, misalnya, membuat instance baru.
interfaceFactory<T>{
T apply();}<T>voidList<T> make10(Factory<T> factory){List<T> result =newArrayList<T>();for(int a =0; a <10; a++)
result.add(factory.apply());return result;}classFooFactory<T>implementsFactory<Foo<T>>{publicFoo<T> apply(){returnnewFoo<T>();}}List<Foo<Integer>> foos = make10(newFooFactory<Integer>());
@ Ricky Clarkson: Saya tidak melihat bagaimana pabrik ini harus mengembalikan foo parametrized. Bisakah Anda jelaskan bagaimana Anda mendapatkan Foo <T> dari ini? Sepertinya saya ini hanya memberikan Foo yang tidak diparameterisasi. Bukankah T di make10 hanya Foo di sini?
ib84
@ ib84 Saya sudah memperbaiki kode; Sepertinya saya telah melewatkan bahwa Foo parameterisasi ketika saya menulis jawaban awalnya.
Ricky Clarkson
9
Saya punya masalah ini di kelas generik abstrak. Dalam kasus khusus ini, solusinya lebih sederhana:
Ya itu, tetapi saya ingin meninggalkan sangat minimum yang perlu dilakukan saat memperpanjang kelas ini. Periksa jawaban droidpl
Paramvir Singh Karwal
5
Saya punya solusi (jelek tapi efektif) untuk masalah ini, yang saya gunakan baru-baru ini:
import java.lang.reflect.TypeVariable;publicstatic<T>Class<T> getGenericClass(){
__<T> ins =new __<T>();TypeVariable<?>[] cls = ins.getClass().getTypeParameters();return(Class<T>)cls[0].getClass();}privatefinalclass __<T>// generic helper class which does only provide type information{private __(){}}
Saya menemukan cara umum dan sederhana untuk melakukan itu. Di kelas saya, saya membuat metode yang mengembalikan tipe generik sesuai dengan posisinya dalam definisi kelas. Mari kita asumsikan definisi kelas seperti ini:
publicclassMyClass<A, B, C>{}
Sekarang mari kita membuat beberapa atribut untuk tetap mengetik:
publicclassMyClass<A, B, C>{privateClass<A> aType;privateClass<B> bType;privateClass<C> cType;// Getters and setters (not necessary if you are going to use them internally)}
Kemudian Anda bisa membuat metode generik yang mengembalikan tipe berdasarkan indeks definisi generik:
/**
* Returns a {@link Type} object to identify generic types
* @return type
*/privateType getGenericClassType(int index){// To make it use generics without supplying the class typeType type = getClass().getGenericSuperclass();while(!(type instanceofParameterizedType)){if(type instanceofParameterizedType){
type =((Class<?>)((ParameterizedType) type).getRawType()).getGenericSuperclass();}else{
type =((Class<?>) type).getGenericSuperclass();}}return((ParameterizedType) type).getActualTypeArguments()[index];}
Akhirnya, dalam konstruktor cukup panggil metode dan kirim indeks untuk setiap jenis. Kode lengkap akan terlihat seperti:
publicclassMyClass<A, B, C>{privateClass<A> aType;privateClass<B> bType;privateClass<C> cType;publicMyClass(){this.aType =(Class<A>) getGenericClassType(0);this.bType =(Class<B>) getGenericClassType(1);this.cType =(Class<C>) getGenericClassType(2);}/**
* Returns a {@link Type} object to identify generic types
* @return type
*/privateType getGenericClassType(int index){Type type = getClass().getGenericSuperclass();while(!(type instanceofParameterizedType)){if(type instanceofParameterizedType){
type =((Class<?>)((ParameterizedType) type).getRawType()).getGenericSuperclass();}else{
type =((Class<?>) type).getGenericSuperclass();}}return((ParameterizedType) type).getActualTypeArguments()[index];}}
Seperti yang dijelaskan dalam jawaban lain, gunakan ini ParameterizedType pendekatan , Anda perlu memperluas kelas, tapi itu sepertinya pekerjaan ekstra untuk membuat kelas baru yang meluas ...
Jadi, membuat abstrak kelas memaksa Anda untuk memperpanjangnya, sehingga memenuhi persyaratan subklasifikasi. (menggunakan @Getter lombok).
Sekarang untuk memperluasnya tanpa mendefinisikan kelas baru. (Catat {} di bagian akhir ... diperpanjang, tetapi jangan menimpa apa pun - kecuali jika Anda mau).
public<T> T yourMethodSignature(Class<T> type){// get some object and check the type match the given typeObject result =...if(type.isAssignableFrom(result.getClass())){return(T)result;}else{// handle the error}}
Jika Anda memperluas atau mengimplementasikan kelas / antarmuka apa saja yang menggunakan generik, Anda bisa mendapatkan Generic Type dari kelas induk / antarmuka, tanpa memodifikasi kelas / antarmuka yang ada sama sekali.
Mungkin ada tiga kemungkinan,
Kasus 1
Saat kelas Anda memperluas kelas yang menggunakan Generics
Itu cukup lurus ke depan. Jika Anda membutuhkan dari dalam kelas yang sama:
Class clazz =this.getClass();ParameterizedType parameterizedType =(ParameterizedType) clazz.getGenericSuperclass();try{Class typeClass =Class.forName( parameterizedType.getActualTypeArguments()[0].getTypeName());// You have the instance of type 'T' in typeClass variableSystem.out.println("Class instance name: "+ typeClass.getName());}catch(ClassNotFoundException e){System.out.println("ClassNotFound!! Something wrong! "+ e.getMessage());}
Sebenarnya, saya kira Anda memiliki bidang di kelas tipe T. Jika tidak ada bidang tipe T, apa gunanya memiliki Tipe generik? Jadi, Anda cukup melakukan instanceof pada bidang itu.
Dalam kasus saya, saya punya
Daftar item <T>;
di kelas saya, dan saya memeriksa apakah tipe kelas "Lokalitas" oleh
if (items.get (0) instance of Locality) ...
Tentu saja, ini hanya berfungsi jika jumlah total kelas yang mungkin terbatas.
classGenericClass<T>(private val rawType:Class<*>){
constructor():this(`$Gson$Types`.getRawType(object :TypeToken<T>(){}.getType()))
fun getRawType():Class<T>{return rawType as Class<T>}}
Saya ingin meneruskan T.class ke metode yang menggunakan Generics
Metode readFile membaca file .csv yang ditentukan oleh fileName dengan fullpath. Mungkin ada file csv dengan konten yang berbeda maka saya harus lulus kelas model file sehingga saya bisa mendapatkan objek yang sesuai. Karena ini membaca file csv yang ingin saya lakukan dengan cara yang umum. Untuk beberapa alasan atau tidak ada solusi di atas yang berfungsi untuk saya. Saya perlu menggunakannya
Class<? extends T> typeuntuk membuatnya bekerja. Saya menggunakan pustaka opencsv untuk mem-parsing file CSV.
import com.fasterxml.jackson.core.type.TypeReference;
new TypeReference<T>(){}
Jawaban:
Jawaban singkatnya adalah, bahwa tidak ada cara untuk mengetahui tipe runtime dari parameter tipe generik di Jawa. Saya sarankan membaca bab tentang penghapusan tipe dalam Tutorial Java untuk lebih jelasnya.
Solusi populer untuk ini adalah untuk melewatkan
Class
parameter tipe ke konstruktor dari tipe generik, misalnyasumber
typeParameterClass
tanpa penugasan default di konstruktor baik-baik saja. Tidak perlu mengaturnya untuk yang kedua kalinya.Saya mencari cara untuk melakukan ini sendiri tanpa menambahkan ketergantungan ekstra ke classpath. Setelah beberapa penyelidikan saya menemukan bahwa itu adalah mungkin selama Anda memiliki supertype generik. Ini tidak masalah bagi saya karena saya bekerja dengan layer DAO dengan supertype layer generik. Jika ini cocok dengan skenario Anda, maka itu adalah pendekatan IMHO yang paling rapi.
Sebagian besar kasus penggunaan generik yang saya temui memiliki semacam supertipe generik misalnya
List<T>
untukArrayList<T>
atauGenericDAO<T>
untukDAO<T>
, dll.Solusi Java murni
Artikel Mengakses tipe generik saat runtime di Java menjelaskan bagaimana Anda dapat melakukannya menggunakan Java murni.
Solusi musim semi
Proyek saya menggunakan Spring yang bahkan lebih baik karena Spring memiliki metode utilitas praktis untuk menemukan tipe. Ini adalah pendekatan terbaik bagi saya karena terlihat rapi. Saya kira jika Anda tidak menggunakan Spring Anda bisa menulis metode utilitas Anda sendiri.
sumber
Namun ada celah kecil: jika Anda mendefinisikan
Foo
kelas Anda sebagai abstrak. Itu berarti Anda harus membuat instantiate kelas Anda sebagai:(Perhatikan kurung ganda di bagian akhir.)
Sekarang Anda dapat mengambil jenis
T
saat runtime:Namun perlu dicatat bahwa
mySuperclass
itu harus menjadi superkelas dari definisi kelas sebenarnya mendefinisikan tipe final untukT
.Ini juga tidak terlalu elegan, tetapi Anda harus memutuskan apakah Anda lebih suka
new Foo<MyType>(){}
ataunew Foo<MyType>(MyType.class);
dalam kode Anda.Sebagai contoh:
Kemudian:
sumber
TypeLiteral
a
danb
dibuat dengan cara ini akan memperluas kelas yang sama tetapi tidak memiliki kelas instance yang identik.a.getClass() != b.getClass()
Pendekatan / solusi / solusi standar adalah dengan menambahkan
class
objek ke konstruktor, seperti:sumber
Bayangkan Anda memiliki superclass abstrak yang generik:
Dan kemudian Anda memiliki kelas kedua yang memanjang Foo dengan Bar generik yang memanjang T:
Anda bisa mendapatkan kelas
Bar.class
di kelas Foo dengan memilihType
(dari jawaban bert bruynooghe) dan menyimpulkannya menggunakanClass
instance:Anda harus mencatat bahwa operasi ini tidak ideal, sehingga merupakan ide bagus untuk men-cache nilai yang dihitung untuk menghindari beberapa perhitungan. Salah satu kegunaan khas adalah dalam implementasi DAO generik.
Implementasi akhir:
Nilai yang dikembalikan adalah Bar.class ketika dipanggil dari kelas Foo di fungsi lain atau dari kelas Bar.
sumber
toString().split(" ")[1]
itulah masalahnya, hindari"class "
Berikut ini solusi yang berhasil:
CATATAN: Dapat digunakan hanya sebagai superclass
Child extends Generic<Integer>
)ATAU
new Generic<Integer>() {};
)sumber
Anda tidak dapat melakukannya karena penghapusan tipe. Lihat juga Stack Overflow pertanyaan Java generics - tipe erasure - kapan dan apa yang terjadi .
sumber
Rute yang lebih baik daripada Kelas yang disarankan orang lain adalah lewat di objek yang dapat melakukan apa yang akan Anda lakukan dengan Kelas, misalnya, membuat instance baru.
sumber
Saya punya masalah ini di kelas generik abstrak. Dalam kasus khusus ini, solusinya lebih sederhana:
dan kemudian pada kelas turunan:
sumber
Saya punya solusi (jelek tapi efektif) untuk masalah ini, yang saya gunakan baru-baru ini:
sumber
Itu mungkin:
Anda memerlukan dua fungsi dari hibernate-generic-dao / blob / master / dao / src / main / java / com / googlecode / genericdao / dao / DAOUtil.java .
Untuk penjelasan lebih lanjut, lihat Mencerminkan obat generik .
sumber
Saya menemukan cara umum dan sederhana untuk melakukan itu. Di kelas saya, saya membuat metode yang mengembalikan tipe generik sesuai dengan posisinya dalam definisi kelas. Mari kita asumsikan definisi kelas seperti ini:
Sekarang mari kita membuat beberapa atribut untuk tetap mengetik:
Kemudian Anda bisa membuat metode generik yang mengembalikan tipe berdasarkan indeks definisi generik:
Akhirnya, dalam konstruktor cukup panggil metode dan kirim indeks untuk setiap jenis. Kode lengkap akan terlihat seperti:
sumber
Seperti yang dijelaskan dalam jawaban lain, gunakan ini
ParameterizedType
pendekatan , Anda perlu memperluas kelas, tapi itu sepertinya pekerjaan ekstra untuk membuat kelas baru yang meluas ...Jadi, membuat abstrak kelas memaksa Anda untuk memperpanjangnya, sehingga memenuhi persyaratan subklasifikasi. (menggunakan @Getter lombok).
Sekarang untuk memperluasnya tanpa mendefinisikan kelas baru. (Catat {} di bagian akhir ... diperpanjang, tetapi jangan menimpa apa pun - kecuali jika Anda mau).
sumber
Saya berasumsi bahwa, karena Anda memiliki kelas generik, Anda akan memiliki variabel seperti itu:
(variabel ini perlu mengambil nilai pada konstruktor)
Dalam hal ini, Anda cukup membuat metode berikut:
Semoga ini bisa membantu!
sumber
sumber
Jika Anda memperluas atau mengimplementasikan kelas / antarmuka apa saja yang menggunakan generik, Anda bisa mendapatkan Generic Type dari kelas induk / antarmuka, tanpa memodifikasi kelas / antarmuka yang ada sama sekali.
Mungkin ada tiga kemungkinan,
Kasus 1 Saat kelas Anda memperluas kelas yang menggunakan Generics
Kasus 2 Saat kelas Anda mengimplementasikan antarmuka yang menggunakan Generics
Kasus 3 Saat antarmuka Anda memperluas antarmuka yang menggunakan Generics
sumber
Itu cukup lurus ke depan. Jika Anda membutuhkan dari dalam kelas yang sama:
sumber
Sebenarnya, saya kira Anda memiliki bidang di kelas tipe T. Jika tidak ada bidang tipe T, apa gunanya memiliki Tipe generik? Jadi, Anda cukup melakukan instanceof pada bidang itu.
Dalam kasus saya, saya punya
di kelas saya, dan saya memeriksa apakah tipe kelas "Lokalitas" olehTentu saja, ini hanya berfungsi jika jumlah total kelas yang mungkin terbatas.
sumber
Pertanyaan ini sudah lama, tetapi sekarang yang terbaik adalah menggunakan google
Gson
.Contoh untuk mendapatkan kustom
viewModel
.Kelas tipe generik
sumber
Saya ingin meneruskan T.class ke metode yang menggunakan Generics
Metode readFile membaca file .csv yang ditentukan oleh fileName dengan fullpath. Mungkin ada file csv dengan konten yang berbeda maka saya harus lulus kelas model file sehingga saya bisa mendapatkan objek yang sesuai. Karena ini membaca file csv yang ingin saya lakukan dengan cara yang umum. Untuk beberapa alasan atau tidak ada solusi di atas yang berfungsi untuk saya. Saya perlu menggunakannya
Class<? extends T> type
untuk membuatnya bekerja. Saya menggunakan pustaka opencsv untuk mem-parsing file CSV.Ini adalah bagaimana metode readFile dipanggil
sumber
Saya menggunakan solusi untuk ini:
sumber