Apakah mungkin membuat instance tipe generik di Jawa? Saya berpikir berdasarkan apa yang saya lihat bahwa jawabannya adalah no
( karena tipe erasure ), tetapi saya akan tertarik jika ada yang bisa melihat sesuatu yang saya lewatkan:
class SomeContainer<E>
{
E createContents()
{
return what???
}
}
EDIT: Ternyata Token Jenis Super dapat digunakan untuk menyelesaikan masalah saya, tetapi memerlukan banyak kode berbasis refleksi, seperti yang ditunjukkan beberapa jawaban di bawah ini.
Saya akan membiarkan ini terbuka sebentar untuk melihat apakah ada orang yang menemukan sesuatu yang secara dramatis berbeda dari Artikel Artima dari Ian Robertson .
Jawaban:
Anda benar. Anda tidak bisa melakukannya
new E()
. Tapi Anda bisa mengubahnya menjadiIni menyakitkan. Tapi itu berhasil. Membungkusnya dalam pola pabrik membuatnya sedikit lebih bisa ditoleransi.
sumber
Class<?>
referensi menggunakan Guava dan TypeToken, lihat jawaban ini untuk kode dan tautan!Tidak tahu apakah ini membantu, tetapi ketika Anda mensubklasifikasikan (termasuk secara anonim) jenis generik, informasi jenis tersedia melalui refleksi. misalnya,
Jadi, ketika Anda subkelas Foo, Anda mendapatkan instance dari Bar misalnya,
Tapi ini banyak pekerjaan, dan hanya berfungsi untuk subclass. Meskipun bisa berguna.
sumber
Foo
tidak abstrak. Tetapi mengapa itu hanya bekerja pada subclass anonim Foo? Misalkan kita membuatFoo
beton (kita tinggalkanabstract
), mengapa akannew Foo<Bar>();
menghasilkan kesalahan, sedangkannew Foo<Bar>(){};
tidak? (Pengecualian: "Kelas tidak dapat dilemparkan ke ParameterizedType")<E>
diclass Foo<E>
tidak terikat untuk jenis tertentu. Anda akan melihat perilaku yang luar biasa setiap kaliE
tidak statis terikat, seperti di:new Foo<Bar>()
,new Foo<T>() {...}
, atauclass Fizz <E> extends Foo<E>
. Kasing pertama tidak terikat secara statis, terhapus pada waktu kompilasi. Kasus kedua menggantikan variabel tipe lain (T) sebagai penggantiE
tetapi masih tidak mengikat. Dan dalam kasus terakhir harus jelas bahwaE
masih tidak terikat.class Fizz extends Foo<Bar>
- dalam hal ini, penggunaFizz
mendapatkan sesuatu yang merupakanFoo<Bar>
dan tidak bisa apa-apa selain aFoo<Bar>
. Jadi dalam hal ini, kompiler dengan senang hati menyandikan informasi itu ke dalam metadata kelas untukFizz
dan menjadikannya tersedia sebagaiParameterizedType
kode refleksi. Ketika Anda membuat kelas dalam anonim sepertinew Foo<Bar>() {...}
itu melakukan hal yang sama, kecuali bukannyaFizz
kompiler menghasilkan nama kelas "anonim" yang Anda tidak akan tahu sampai kelas luar dikompilasi.Foo<Bar<Baz>>
,. Anda akan membuat sebuah instanceParameterizedTypeImpl
yang tidak dapat dibuat secara eksplisit. Karena itu, ada baiknya memeriksa apakahgetActualTypeArguments()[0]
mengembalikan aParameterizedType
. Jika ya, maka Anda ingin mendapatkan jenis mentah, dan membuat instance dari itu.Di Java 8 Anda dapat menggunakan
Supplier
antarmuka fungsional untuk mencapai ini dengan cukup mudah:Anda akan membangun kelas ini seperti ini:
Sintaks
String::new
pada baris itu adalah referensi konstruktor .Jika konstruktor Anda mengambil argumen, Anda dapat menggunakan ekspresi lambda sebagai gantinya:
sumber
SomeContainer stringContainer = new SomeContainer(String::new);
?Anda memerlukan beberapa jenis pabrik abstrak untuk mengoper:
sumber
Factory<>
adalah antarmuka sehingga tidak ada isi. Intinya adalah Anda memerlukan lapisan tipuan untuk meneruskan tanggung jawab ke metode yang "tahu" kode yang diperlukan untuk membangun sebuah instance. Jauh lebih baik melakukan ini dengan kode normal daripada metalinguistikClass
atauConstructor
sebagai refleksi membawa seluruh dunia terluka.SomeContainer<SomeElement> cont = new SomeContainer<>(SomeElement::new);
sumber
class GenericHome<T> extends Home<T>{}
Jika Anda memerlukan contoh baru dari argumen tipe di dalam kelas generik maka buat konstruktor Anda menuntut kelasnya ...
Pemakaian:
Pro:
Cons:
Foo<L>
bukti. Sebagai permulaan ...newInstance()
akan melempar wobbler jika kelas argumen type tidak memiliki konstruktor default. Ini berlaku untuk semua solusi yang diketahui.sumber
Anda dapat melakukan ini sekarang dan tidak memerlukan banyak kode refleksi.
Tentu saja jika Anda perlu memanggil konstruktor yang akan memerlukan beberapa refleksi, tetapi itu didokumentasikan dengan sangat baik, trik ini tidak!
Berikut ini adalah JavaDoc untuk TypeToken .
sumber
Pikirkan tentang pendekatan yang lebih fungsional: alih-alih membuat beberapa E dari ketiadaan (yang jelas merupakan bau kode), berikan fungsi yang tahu cara membuatnya, yaitu
sumber
Exception
digunakanSupplier<E>
sebagai gantinya.Dari Tutorial Java - Pembatasan Generik :
Tidak Dapat Membuat Contoh Parameter Tipe
Anda tidak dapat membuat turunan dari parameter tipe. Misalnya, kode berikut ini menyebabkan kesalahan waktu kompilasi:
Sebagai solusinya, Anda bisa membuat objek parameter tipe melalui refleksi:
Anda dapat menggunakan metode append sebagai berikut:
sumber
Berikut adalah opsi yang saya buat, mungkin membantu:
EDIT: Atau Anda dapat menggunakan konstruktor ini (tetapi membutuhkan instance E):
sumber
Jika Anda tidak ingin mengetikkan nama kelas dua kali selama instantiation seperti di:
Anda dapat menggunakan metode pabrik:
Seperti di:
sumber
Sayangnya Java tidak mengizinkan apa yang ingin Anda lakukan. Lihat solusi resmi :
sumber
Ketika Anda bekerja dengan E pada waktu kompilasi, Anda tidak terlalu peduli dengan tipe generik "E" yang sebenarnya (baik Anda menggunakan refleksi atau bekerja dengan kelas dasar dari tipe generik) jadi biarkan subclass memberikan instance E.
sumber
Kamu bisa menggunakan:
Tetapi Anda perlu memberikan nama kelas yang tepat, termasuk paket, misalnya.
java.io.FileInputStream
. Saya menggunakan ini untuk membuat parser ekspresi matematika.sumber
foo.getClass().getName()
. Dari mana contoh itu berasal? Saya saat ini menyerahkan satu ke konstruktor dalam proyek yang sedang saya kerjakan.Semoga ini belum terlambat untuk membantu !!!
Java adalah tipe-safety, hanya Object yang bisa membuat instance.
Dalam kasus saya, saya tidak bisa meneruskan parameter ke
createContents
metode. Solusi saya menggunakan ekstensi bukan semua jawaban di bawah.Ini contoh kasus saya yang saya tidak bisa melewati parameter.
Menggunakan refleksi membuat galat run time, jika Anda memperluas kelas generik Anda tanpa tipe objek. Untuk memperluas tipe generik Anda ke objek, ubah kesalahan ini menjadi kompilasi kesalahan waktu.
sumber
Gunakan
TypeToken<T>
kelas:sumber
(T) new TypeToken<T>(getClass()){}.getRawType().newInstance();
Saya pikir saya bisa melakukan itu, tetapi cukup kecewa: itu tidak berhasil, tetapi saya pikir itu masih layak untuk dibagikan.
Mungkin seseorang dapat memperbaiki:
Itu menghasilkan:
Jalur 26 adalah satu dengan
[*]
.Satu-satunya solusi yang layak adalah solusi oleh @JustinRudd
sumber
Sebuah jawaban penting dari jawaban Nuh.
Alasan untuk berubah
Sebuah] aman jika lebih dari 1 jenis generik digunakan jika Anda mengubah urutan.
b] Tanda tangan tipe generik kelas berubah dari waktu ke waktu sehingga Anda tidak akan terkejut dengan pengecualian yang tidak dijelaskan dalam runtime.
Kode yang Kuat
Atau gunakan liner satu
Kode Satu Baris
sumber
apa yang bisa kamu lakukan adalah -
Pertama mendeklarasikan variabel kelas generik itu
2.Kemudian buatlah konstruktor dan buat objek itu
Kemudian gunakan di mana pun Anda ingin menggunakannya
contoh-
1
2
3.
sumber
Seperti yang Anda katakan, Anda tidak dapat benar-benar melakukannya karena penghapusan tipe. Anda dapat mengurutkannya menggunakan refleksi, tetapi membutuhkan banyak kode dan banyak penanganan kesalahan.
sumber
Jika Anda maksud
new E()
maka itu tidak mungkin. Dan saya akan menambahkan bahwa itu tidak selalu benar - bagaimana Anda tahu jika E memiliki konstruktor tanpa argumen publik? Tetapi Anda selalu dapat mendelegasikan kreasi ke beberapa kelas lain yang tahu cara membuat instance - bisaClass<E>
atau kode kustom Anda seperti inisumber
sumber
SomeContainer
sederhanaObject
. Oleh karena itu,this.getClass().getGenericSuperclass()
mengembalikanClass
(kelas java.lang.Object), bukan aParameterizedType
. Ini sebenarnya sudah ditunjukkan oleh stackoverflow.com/questions/75175/… pertanyaan rekan juga.Anda dapat mencapai ini dengan cuplikan berikut:
sumber
Ada berbagai perpustakaan yang dapat menyelesaikan
E
untuk Anda menggunakan teknik yang mirip dengan apa yang dibahas artikel Robertson. Inilah implementasicreateContents
yang menggunakan TypeTools untuk menyelesaikan kelas mentah yang diwakili oleh E:Ini mengasumsikan bahwa getClass () memutuskan ke subkelas SomeContainer dan akan gagal jika tidak karena nilai parameter aktual E akan dihapus saat runtime jika tidak ditangkap dalam subkelas.
sumber
Berikut ini adalah implementasi dari
createContents
yang menggunakan TypeTools untuk menyelesaikan kelas mentah yang diwakili olehE
:Pendekatan ini hanya berfungsi jika
SomeContainer
disubklasifikasikan sehingga nilai aktualE
ditangkap dalam definisi tipe:Kalau tidak, nilai E dihapus saat runtime dan tidak dapat dipulihkan.
sumber
Anda bisa dengan classloader dan nama kelas, akhirnya beberapa parameter.
sumber
Berikut adalah solusi yang ditingkatkan, berdasarkan
ParameterizedType.getActualTypeArguments
, sudah disebutkan oleh @noah, @Lars Bohl, dan beberapa lainnya.Peningkatan kecil pertama dalam implementasi. Pabrik tidak boleh mengembalikan instance, tetapi tipe. Segera setelah Anda kembali contoh menggunakan
Class.newInstance()
Anda mengurangi ruang lingkup penggunaan. Karena hanya konstruktor tanpa argumen yang dapat dipanggil seperti ini. Cara yang lebih baik adalah mengembalikan tipe, dan mengizinkan klien untuk memilih, konstruktor mana yang ingin dia panggil:Berikut adalah contoh penggunaannya. @ Lars Bohl hanya menunjukkan cara signe untuk mendapatkan genenerik terverifikasi melalui ekstensi. @noah hanya melalui membuat instance dengan
{}
. Berikut adalah tes untuk menunjukkan kedua kasus:Catatan: Anda dapat memaksa klien
TypeReference
selalu digunakan{}
ketika misalnya dibuat dengan membuat kelas ini abstrak:public abstract class TypeReference<T>
. Saya belum melakukannya, hanya untuk menunjukkan test case terhapus.sumber