Karena penerapan generik Java, Anda tidak dapat memiliki kode seperti ini:
public class GenSet<E> {
private E a[];
public GenSet() {
a = new E[INITIAL_ARRAY_LENGTH]; // error: generic array creation
}
}
Bagaimana saya bisa menerapkan ini sambil menjaga keamanan jenis?
Saya melihat solusi di forum Java yang berjalan seperti ini:
import java.lang.reflect.Array;
class Stack<T> {
public Stack(Class<T> clazz, int capacity) {
array = (T[])Array.newInstance(clazz, capacity);
}
private final T[] array;
}
Tetapi saya benar-benar tidak mengerti apa yang sedang terjadi.
java
arrays
generics
reflection
instantiation
tatsuhirosatou
sumber
sumber
Jawaban:
Sebagai gantinya, saya harus mengajukan pertanyaan: apakah
GenSet
"dicentang" atau "tidak dicentang"? Apa artinya?Diperiksa : ketikan yang kuat .
GenSet
tahu secara eksplisit jenis objek yang dikandungnya (yaitu konstruktornya secara eksplisit dipanggil denganClass<E>
argumen, dan metode akan mengeluarkan pengecualian ketika mereka memberikan argumen yang bukan tipeE
. LihatCollections.checkedCollection
.-> dalam hal ini, Anda harus menulis:
Tidak dicentang : pengetikan lemah . Tidak ada pemeriksaan tipe yang benar-benar dilakukan pada objek yang dilewatkan sebagai argumen.
-> dalam hal ini, Anda harus menulis
Perhatikan bahwa tipe komponen array harus menjadi penghapusan parameter tipe:
Semua ini merupakan hasil dari kelemahan generik yang diketahui dan disengaja di Jawa: ini diimplementasikan dengan menggunakan penghapusan, jadi kelas "generik" tidak tahu argumen tipe apa yang mereka buat pada saat run time, dan oleh karena itu tidak dapat memberikan tipe- keamanan kecuali jika beberapa mekanisme eksplisit (pengecekan tipe) diimplementasikan.
sumber
Object[] EMPTY_ELEMENTDATA = {}
untuk penyimpanan. Bisakah saya menggunakan mekanisme ini untuk mengubah ukuran tanpa mengetahui jenisnya menggunakan obat generik?public void <T> T[] newArray(Class<T> type, int length) { ... }
Kamu bisa melakukan ini:
Ini adalah salah satu cara yang disarankan untuk mengimplementasikan koleksi generik di Java yang Efektif; Butir 26 . Tidak ada kesalahan jenis, tidak perlu melemparkan array berulang kali. Namun ini memicu peringatan karena berpotensi berbahaya, dan harus digunakan dengan hati-hati. Sebagaimana dirinci dalam komentar, ini
Object[]
sekarang menyamar sebagaiE[]
tipe kami , dan dapat menyebabkan kesalahan tak terdugaClassCastException
jika digunakan secara tidak aman.Sebagai aturan praktis, perilaku ini aman selama array cor digunakan secara internal (misalnya untuk mendukung struktur data), dan tidak dikembalikan atau diekspos ke kode klien. Jika Anda perlu mengembalikan larik tipe generik ke kode lain,
Array
kelas refleksi yang Anda sebutkan adalah cara yang tepat.Layak disebutkan bahwa jika memungkinkan, Anda akan memiliki waktu yang jauh lebih bahagia bekerja dengan
List
s daripada array jika Anda menggunakan obat generik. Tentu saja kadang-kadang Anda tidak punya pilihan, tetapi menggunakan kerangka koleksi jauh lebih kuat.sumber
String[] s=b;
padatest()
metode di atas . Itu karena array E tidak benar-benar, itu Object []. Ini penting jika Anda ingin, misalnya aList<String>[]
- Anda tidak dapat menggunakanObject[]
untuk itu, Anda harus memilikiList[]
khusus. Itulah sebabnya Anda perlu menggunakan kreasi array Kelas <?> Yang direfleksikan.public E[] toArray() { return (E[])internalArray.clone(); }
ketikainternalArray
diketik sebagaiE[]
, dan karena itu sebenarnya merupakanObject[]
. Ini gagal saat runtime dengan pengecualian tipe-casting karena suatuObject[]
tidak dapat ditugaskan ke array dari jenis apa pun yangE
terjadi.E[] b = (E[])new Object[1];
Anda dapat dengan jelas melihat bahwa satu-satunya referensi ke array yang dibuat adalahb
dan jenisnyab
adalahE[]
. Oleh karena itu tidak ada bahaya Anda secara tidak sengaja mengakses array yang sama melalui variabel berbeda dari tipe yang berbeda. Jika sebaliknya,Object[] a = new Object[1]; E[]b = (E[])a;
maka Anda harus paranoid tentang cara Anda menggunakannyaa
.Berikut ini cara menggunakan obat generik untuk mendapatkan larik tipe tepat yang Anda cari sambil menjaga keamanan tipe (berbeda dengan jawaban lain, yang akan mengembalikan
Object
array atau menghasilkan peringatan pada waktu kompilasi):Itu mengkompilasi tanpa peringatan, dan seperti yang Anda lihat
main
, untuk tipe apa pun yang Anda nyatakan instanceGenSet
as, Anda bisa menetapkana
ke array tipe itu, dan Anda bisa menetapkan elemen daria
ke variabel tipe itu, artinya array itu dan nilai-nilai dalam array adalah tipe yang benar.Ia bekerja dengan menggunakan literal kelas sebagai token jenis runtime, seperti yang dibahas dalam Tutorial Java . Literal kelas diperlakukan oleh kompiler sebagai instance dari
java.lang.Class
. Untuk menggunakannya, cukup ikuti nama kelas dengan.class
. Jadi,String.class
bertindak sebagaiClass
objek yang mewakili kelasString
. Ini juga berfungsi untuk antarmuka, enum, array dimensi apa pun (misalnyaString[].class
), primitif (mis.int.class
), Dan kata kuncivoid
(yaituvoid.class
).Class
itu sendiri generik (dideklarasikan sebagaiClass<T>
, di manaT
singkatan dari tipe yangClass
diwakili objek), artinya tipeString.class
isClass<String>
.Jadi, setiap kali Anda memanggil konstruktor
GenSet
, Anda meneruskan literal kelas untuk argumen pertama yang mewakili array dari tipeGenSet
instance yang dinyatakan (misalnyaString[].class
untukGenSet<String>
). Perhatikan bahwa Anda tidak akan bisa mendapatkan array primitif, karena primitif tidak dapat digunakan untuk variabel tipe.Di dalam konstruktor, memanggil metode
cast
mengembalikanObject
argumen yang dilewatkan ke kelas yang diwakili olehClass
objek tempat metode dipanggil. Memanggil metode statisnewInstance
dalamjava.lang.reflect.Array
pengembalian sebagaiObject
array dari tipe yang diwakili olehClass
objek yang dilewati sebagai argumen pertama dan panjang yang ditentukan oleh yangint
dilewati sebagai argumen kedua. Memanggil metodegetComponentType
mengembalikanClass
objek yang mewakili jenis komponen array yang diwakili olehClass
objek tempat metode dipanggil (misalnyaString.class
untukString[].class
,null
jikaClass
objek tidak mewakili array).Kalimat terakhir itu tidak sepenuhnya akurat. Memanggil
String[].class.getComponentType()
mengembalikanClass
objek yang mewakili kelasString
, tetapi tipenya adalahClass<?>
, bukanClass<String>
, itulah sebabnya Anda tidak bisa melakukan sesuatu seperti berikut ini.Hal yang sama berlaku untuk setiap metode
Class
yang mengembalikanClass
objek.Mengenai komentar Joachim Sauer tentang jawaban ini (saya sendiri tidak memiliki cukup reputasi untuk mengomentarinya), contoh menggunakan pemeran
T[]
akan menghasilkan peringatan karena kompiler tidak dapat menjamin keamanan jenis dalam kasus itu.Edit tentang komentar Ingo:
sumber
Ini adalah satu-satunya jawaban yang aman jenis
sumber
Arrays#copyOf()
tidak tergantung pada panjang array yang disediakan sebagai argumen pertama. Itu pintar, meskipun itu membayar biaya panggilan keMath#min()
danSystem#arrayCopy()
, tidak ada yang benar-benar diperlukan untuk menyelesaikan pekerjaan ini. docs.oracle.com/javase/7/docs/api/java/util/…E
merupakan variabel tipe. The varargs menciptakan sebuah array penghapusanE
ketikaE
adalah variabel tipe, membuatnya tidak jauh berbeda dari(E[])new Object[n]
. Silakan lihat http://ideone.com/T8xF91 . Itu tidak berarti lebih aman daripada jawaban lainnya.new E[]
. Masalah yang Anda tunjukkan dalam contoh Anda adalah masalah penghapusan umum, tidak unik untuk pertanyaan ini dan jawaban ini.Untuk memperluas ke dimensi yang lebih, cukup tambahkan
[]
parameter 's dan dimensi kenewInstance()
(T
adalah parameter tipe,cls
adalah aClass<T>
,d1
throughd5
adalah bilangan bulat):Lihat
Array.newInstance()
detailnya.sumber
Di Java 8, kita bisa melakukan semacam pembuatan array generik menggunakan lambda atau referensi metode. Ini mirip dengan pendekatan reflektif (yang melewati a
Class
), tetapi di sini kita tidak menggunakan refleksi.Sebagai contoh, ini digunakan oleh
<A> A[] Stream.toArray(IntFunction<A[]>)
.Ini bisa juga dilakukan pre-Jawa 8 menggunakan kelas anonim tapi lebih rumit.
sumber
ArraySupplier
ini, Anda dapat mendeklarasikan konstruktorGenSet(Supplier<E[]> supplier) { ...
dan menyebutnya dengan garis yang sama seperti yang Anda miliki.IntFunction<E[]>
, tapi ya itu benar.Ini dicakup dalam Bab 5 (Generik) dari Java Efektif, Edisi ke-2 , item 25 ... Memilih daftar ke array
Kode Anda akan berfungsi, meskipun akan menghasilkan peringatan yang tidak dicentang (yang dapat Anda tekan dengan anotasi berikut:
Namun, mungkin akan lebih baik menggunakan Daftar daripada Array.
Ada diskusi menarik tentang bug / fitur ini di situs proyek OpenJDK .
sumber
Anda tidak perlu meneruskan argumen Kelas ke konstruktor. Coba ini.
dan
hasil:
sumber
Java generics bekerja dengan memeriksa jenis pada waktu kompilasi dan memasukkan gips yang sesuai, tetapi menghapus jenis-jenis tersebut di file yang dikompilasi. Ini membuat pustaka generik dapat digunakan oleh kode yang tidak memahami generik (yang merupakan keputusan desain yang disengaja) tetapi yang berarti Anda biasanya tidak bisa mengetahui apa jenisnya saat dijalankan.
Masyarakat
Stack(Class<T> clazz,int capacity)
konstruktor mengharuskan Anda untuk melewati sebuah objek Kelas pada waktu berjalan, mana informasi berarti kelas yang tersedia saat runtime ke kode yang membutuhkannya. DanClass<T>
bentuk itu berarti bahwa kompiler akan memeriksa bahwa objek Kelas yang Anda lewati adalah objek Kelas yang tepat untuk tipe T. Bukan subkelas T, bukan superclass T, tetapi tepatnya T.Ini kemudian berarti bahwa Anda dapat membuat objek array dari tipe yang sesuai di konstruktor Anda, yang berarti bahwa jenis objek yang Anda simpan di koleksi Anda akan diperiksa tipenya pada titik mereka ditambahkan ke koleksi.
sumber
Hai meskipun utasnya sudah mati, saya ingin menarik perhatian Anda pada ini:
Generik digunakan untuk memeriksa tipe selama waktu kompilasi:
Jangan khawatir tentang peringatan typecasting ketika Anda menulis kelas generik. Khawatir saat Anda menggunakannya.
sumber
Bagaimana dengan solusi ini?
Ini bekerja dan terlihat terlalu sederhana untuk menjadi kenyataan. Apakah ada kekurangan?
sumber
T[]
, maka Anda tidak dapat secara sistematis membangun aT[] elems
untuk diteruskan ke fungsi. Dan jika Anda bisa, Anda tidak perlu fungsinya.Lihat juga kode ini:
Itu mengkonversi daftar jenis objek ke array dari jenis yang sama.
sumber
List
memiliki lebih dari satu jenis objek di dalamnya misalnyatoArray(Arrays.asList("abc", new Object()))
akan melemparArrayStoreException
.for
lingkaran dan lainnya saya gunakanArrays.fill(res, obj);
karena saya ingin nilai yang sama untuk setiap indeks.Saya telah menemukan cara cepat dan mudah yang berfungsi untuk saya. Perhatikan bahwa saya hanya menggunakan ini pada Java JDK 8. Saya tidak tahu apakah ini akan bekerja dengan versi sebelumnya.
Meskipun kami tidak dapat membuat instance array generik dari parameter tipe tertentu, kami dapat meneruskan array yang sudah dibuat ke konstruktor kelas generik.
Sekarang di main kita bisa membuat array seperti ini:
Untuk lebih fleksibel dengan array Anda, Anda dapat menggunakan daftar tertaut misalnya. ArrayList dan metode lain yang ditemukan di kelas Java.util.ArrayList.
sumber
Contohnya menggunakan refleksi Java untuk membuat array. Melakukan ini umumnya tidak dianjurkan, karena itu bukan typesafe. Sebagai gantinya, yang harus Anda lakukan hanyalah menggunakan Daftar internal, dan hindari array sama sekali.
sumber
Melewati daftar nilai ...
sumber
Saya membuat potongan kode ini untuk secara reflektif membuat instance kelas yang dilewatkan untuk utilitas tes otomatis sederhana.
Perhatikan segmen ini:
untuk array yang memulai di mana Array.newInstance (kelas array, ukuran array) . Kelas dapat berupa primitif (int.class) dan objek (Integer.class).
BeanUtils adalah bagian dari Spring.
sumber
Sebenarnya cara yang lebih mudah untuk melakukannya, adalah membuat array objek dan melemparkannya ke tipe yang Anda inginkan seperti contoh berikut:
di mana
SIZE
konstanta danT
pengenal tipesumber
Para pemeran paksa yang disarankan oleh orang lain tidak bekerja untuk saya, kecuali pengecoran ilegal.
Namun, pemeran implisit ini bekerja dengan baik:
di mana Item adalah kelas yang saya definisikan mengandung anggota:
Dengan cara ini Anda mendapatkan larik tipe K (jika item hanya memiliki nilai) atau tipe generik apa pun yang ingin Anda tetapkan dalam Item kelas.
sumber
Tidak ada orang lain yang menjawab pertanyaan tentang apa yang terjadi dalam contoh yang Anda posting.
Seperti yang orang lain katakan generik "dihapus" selama kompilasi. Jadi pada saat runtime instance dari generik tidak tahu apa tipe komponennya. Alasan untuk ini adalah historis, Sun ingin menambahkan obat generik tanpa merusak antarmuka yang ada (baik sumber dan biner).
Array di sisi lain do tahu jenis komponen mereka pada saat runtime.
Contoh ini mengatasi masalah dengan meminta kode yang memanggil konstruktor (yang tahu tipe) melewati parameter yang memberi tahu kelas tipe yang diperlukan.
Jadi aplikasi akan membangun kelas dengan sesuatu seperti
dan konstruktor sekarang tahu (saat runtime) apa tipe komponen itu dan dapat menggunakan informasi itu untuk membangun array melalui API refleksi.
Akhirnya kita memiliki tipe cast karena kompiler tidak memiliki cara untuk mengetahui bahwa array yang dikembalikan oleh
Array#newInstance()
adalah tipe yang benar (walaupun kita tahu).Gaya ini agak jelek tapi kadang-kadang bisa menjadi solusi paling buruk untuk membuat tipe generik yang perlu mengetahui tipe komponen mereka saat runtime untuk alasan apa pun (membuat array, atau membuat contoh dari jenis komponen mereka, dll.).
sumber
Saya menemukan semacam pekerjaan untuk masalah ini.
Baris di bawah ini menampilkan kesalahan pembuatan array generik
Namun jika saya merangkum
List<Person>
dalam kelas yang terpisah, itu berfungsi.Anda dapat mengekspos orang-orang di kelas PersonList melalui pengambil. Baris di bawah ini akan memberi Anda sebuah array, yang memiliki
List<Person>
dalam setiap elemen. Dengan kata lain susunanList<Person>
.Saya membutuhkan sesuatu seperti ini dalam beberapa kode yang sedang saya kerjakan dan inilah yang saya lakukan untuk membuatnya bekerja. Sejauh ini tidak ada masalah.
sumber
Anda bisa membuat array Obyek dan melemparkannya ke E di mana-mana. Ya, itu bukan cara yang sangat bersih untuk melakukannya tetapi setidaknya harus bekerja.
sumber
coba ini.
sumber
Element
kelas Anda berasal?Sebuah solusi yang mudah, meskipun berantakan untuk ini adalah untuk bersarang kelas "pemegang" kedua di dalam kelas utama Anda, dan menggunakannya untuk menyimpan data Anda.
sumber
new Holder<Thing>[10]
adalah pembuatan array generik.Mungkin tidak terkait dengan pertanyaan ini tetapi ketika saya mendapatkan "
generic array creation
" kesalahan untuk menggunakanSaya menemukan karya-karya berikut (dan bekerja untuk saya) dengan
@SuppressWarnings({"unchecked"})
:sumber
Saya ingin tahu apakah kode ini akan membuat array generik yang efektif?
Sunting: Mungkin cara alternatif untuk membuat array seperti itu, jika ukuran yang Anda butuhkan diketahui dan kecil, akan dengan cukup memberi makan jumlah "null" yang diperlukan ke dalam perintah zeroArray?
Meskipun jelas ini tidak serbaguna seperti menggunakan kode createArray.
sumber
T
kapanT
variabel tipe, yaituzeroArray
mengembalikan sebuahObject[]
. Lihat http://ideone.com/T8xF91 .Anda bisa menggunakan gips:
sumber
a
ke luar kelas!Saya benar-benar menemukan solusi yang cukup unik untuk memotong ketidakmampuan untuk memulai array generik. Yang harus Anda lakukan adalah membuat kelas yang mengambil variabel T generik seperti:
dan kemudian di kelas array Anda mulai saja seperti ini:
memulai
new Generic Invoker[]
akan menyebabkan masalah dengan tidak dicentang tetapi seharusnya tidak ada masalah.Untuk mendapatkan dari array, Anda harus memanggil array [i] .variable seperti:
Sisanya, seperti mengubah ukuran array dapat dilakukan dengan Arrays.copyOf () seperti:
Dan fungsi add dapat ditambahkan seperti ini:
sumber
T
, bukan array dari beberapa tipe parameter.Menurut vnportnoy sintaks
membuat array referensi nol, untuk diisi sebagai
yang merupakan tipe aman.
sumber
sumber
Pembuatan array generik tidak diizinkan di java tetapi Anda dapat melakukannya seperti
sumber