Java Generics adalah salah satu perubahan paling signifikan dalam sejarah bahasa Java. Generik yang tersedia dengan Java 5 telah menjadikan penggunaan Java Collection Framework lebih mudah, lebih nyaman, dan lebih aman. Kesalahan yang terkait dengan penggunaan tipe yang salah sekarang terdeteksi pada tahap kompilasi. Ya, dan bahasa Jawa sendiri menjadi lebih aman. Terlepas dari kesederhanaan jenis generik yang tampak, banyak pengembang mengalami kesulitan menggunakannya. Dalam posting ini saya akan berbicara tentang fitur-fitur bekerja dengan Java Generics, sehingga Anda memiliki lebih sedikit kesulitan ini. Berguna jika Anda bukan guru umum, dan akan membantu menghindari banyak kesulitan saat membahas topik tersebut.

Bekerja dengan koleksi
Misalkan bank perlu menghitung jumlah tabungan di rekening pelanggan. Sebelum munculnya "obat generik," metode penghitungan jumlah tampak seperti ini:
public long getSum(List accounts) { long sum = 0; for (int i = 0, n = accounts.size(); i < n; i++) { Object account = accounts.get(i); if (account instanceof Account) { sum += ((Account) account).getAmount(); } } return sum; }
Kami mengulang, menelusuri daftar akun, dan memeriksa apakah elemen dari daftar ini benar-benar turunan dari kelas
Account
- yaitu, akun pengguna. Jenis objek kami dari kelas
Account
dan metode
getAmount
, yang mengembalikan jumlah dalam akun ini. Kemudian mereka menyimpulkan semuanya dan mengembalikan jumlah total. Diperlukan dua langkah:
if (account instanceof Account) {
sum += ((Account) account).getAmount();
Jika Anda tidak memeriksa (
instanceof
) dari milik kelas
Account
, maka pada tahap kedua
ClassCastException
dimungkinkan - yaitu, crash program. Karena itu, cek semacam itu wajib.
Dengan munculnya Generics, kebutuhan untuk pengecekan dan pengecoran tipe telah menghilang:
public long getSum2(List<Account> accounts) { long sum = 0; for (Account account : accounts) { sum += account.getAmount(); } return sum; }
Sekarang metode
getSum2(List<Account> accounts)
menerima sebagai argumen hanya daftar objek
Account
kelas. Pembatasan ini ditunjukkan dalam metode itu sendiri, dalam tanda tangannya, programmer tidak dapat mentransfer daftar lain - hanya daftar akun klien.
Kita tidak perlu memeriksa tipe elemen dari daftar ini: itu tersirat oleh deskripsi tipe parameter metode
List<Account> accounts
(dapat dibaca sebagai
Account
). Dan kompiler akan melempar kesalahan jika terjadi kesalahan - yaitu, jika seseorang mencoba meneruskan daftar objek selain kelas
Account
ke metode ini.
Pada baris kedua cek, kebutuhan juga hilang. Jika perlu,
casting
akan dilakukan pada tahap kompilasi.
Prinsip substitusi
Prinsip substitusi dari Barbara Liskov adalah definisi spesifik dari subtipe dalam pemrograman berorientasi objek. Gagasan Liskov tentang βsubtipeβ mendefinisikan konsep substitusi: jika
S
adalah subtipe dari
T
, maka objek tipe
T
dalam suatu program dapat digantikan oleh objek tipe
S
tanpa ada perubahan pada sifat yang diinginkan dari program ini.
Jenis
| Subtipe
|
Nomor
| Integer
|
Daftar <E>
| ArrayList <E>
|
Koleksi <E>
| Daftar <E>
|
Berterima kasih <E>
| Koleksi <E>
|
Contoh / Jenis Hubungan ContohBerikut adalah contoh penggunaan prinsip substitusi di Jawa:
Number n = Integer.valueOf(42); List<Number> aList = new ArrayList<>(); Collection<Number> aCollection = aList; Iterable<Number> iterable = aCollection;
Integer
adalah subtipe dari
Number
, oleh karena itu, variabel
n
type
Number
dapat diberi nilai yang dikembalikan oleh metode
Integer.valueOf(42)
.
Kovarian, contravariance dan invarian
Pertama, sedikit teori. Kovarian adalah pelestarian hierarki pewarisan tipe sumber dalam tipe turunan dalam urutan yang sama. Misalnya, jika
Kucing adalah subtipe
Hewan , maka Perangkat
<Cats> adalah subtipe
dari Perangkat <Hewan> . Oleh karena itu, dengan mempertimbangkan prinsip substitusi, seseorang dapat melakukan penugasan berikut:
Many <Hewan> = Banyak <Cats>Contravariance adalah pembalikan hirarki tipe sumber dalam tipe turunan. Misalnya, jika
Kucing adalah subtipe
, maka
Set <Hewan> adalah subtipe dari
Set <Cat> . Oleh karena itu, dengan mempertimbangkan prinsip substitusi, seseorang dapat melakukan penugasan berikut:
Many <Cats> = Many <Animals>Invariance - kurangnya pewarisan antara tipe turunan. Jika
Kucing adalah subtipe
Hewan , maka Perangkat
<Cats> bukan subtipe
dari Perangkat <Hewan> dan Perangkat
<Hewan> bukan subtipe
dari Perangkat <Cat> .
Array di Jawa adalah kovarian . Tipe
S[]
adalah subtipe dari
T[]
jika
S
adalah subtipe dari
T
Contoh Penugasan:
String[] strings = new String[] {"a", "b", "c"}; Object[] arr = strings;
Kami menetapkan tautan ke array string ke
arr
variabel, jenisnya adalah
Β« Β»
. Jika array bukan kovarian, kami tidak akan dapat melakukan ini. Java memungkinkan Anda untuk melakukan ini, program mengkompilasi dan berjalan tanpa kesalahan.
arr[0] = 42;
Tetapi jika kita mencoba mengubah isi array melalui variabel
arr
dan menulis angka 42 di sana, kita akan mendapatkan
ArrayStoreException
pada tahap eksekusi program, karena 42 bukan string, tetapi angka. Ini adalah kelemahan dari kovarians array Java: kita tidak dapat melakukan pemeriksaan pada tahap kompilasi, dan sesuatu mungkin sudah rusak saat runtime.
"Generik" tidak berubah. Berikut ini sebuah contoh:
List<Integer> ints = Arrays.asList(1,2,3); List<Number> nums = ints;
Jika Anda mengambil daftar bilangan bulat, maka itu bukan subtipe dari
Number
, atau subtipe lainnya. Dia hanya subtipe dari dirinya sendiri. Yaitu,
List <Integer>
adalah
List<Integer>
dan tidak ada yang lain. Compiler akan memastikan bahwa variabel
ints
dideklarasikan sebagai daftar objek kelas
Integer yang hanya berisi objek kelas
Integer
dan tidak ada yang lain. Pada tahap kompilasi, pemeriksaan dilakukan, dan tidak ada yang akan jatuh pada runtime kami.
Kartu liar
Apakah Generik selalu invarian? Tidak. Saya akan memberikan contoh:
List<Integer> ints = new ArrayList<Integer>(); List<? extends Number> nums = ints;
Ini adalah kovarians.
List<Integer>
- subtipe
List<? extends Number>
List<? extends Number>
List<Number> nums = new ArrayList<Number>(); List<? super Integer> ints = nums;
Ini adalah penghinaan.
List<Number>
adalah subtipe
List<? super Integer>
List<? super Integer>
.
Catatan seperti
"? extends ..."
atau
"? super ..."
disebut wildcard atau wildcard, dengan batas atas (
extends
) atau batas bawah (
super
).
List<? extends Number>
List<? extends Number>
dapat berisi objek yang kelasnya
Number
atau diwarisi dari
Number
.
List<? super Number>
List<? super Number>
dapat berisi objek yang kelasnya
Number
atau yang
Number
adalah pewarisnya (supertype from
Number
).

| meluas B - wildcard dengan batas atas super B - wildcard dengan batas bawah di mana B - mewakili perbatasan
Catatan bentuk T2 <= T1 berarti bahwa himpunan tipe yang dijelaskan oleh T2 adalah himpunan bagian dari himpunan tipe yang dijelaskan oleh T 1
yaitu Angka <=? memperpanjang Object ? memperpanjang Number <=? memperpanjang Object dan ? super Object <=? nomor super
|
Lebih banyak interpretasi matematis dari topikSepasang tugas untuk menguji pengetahuan:
1. Mengapa kesalahan waktu kompilasi dalam contoh di bawah ini? Nilai apa yang bisa saya tambahkan ke daftar
nums
?
List<Integer> ints = new ArrayList<Integer>(); ints.add(1); ints.add(2); List<? extends Number> nums = ints; nums.add(3.14);
JawabannyaHaruskah wadah dideklarasikan dengan wildcard ? extends
? extends
, Anda hanya bisa membaca nilai. Tidak ada yang bisa ditambahkan ke daftar kecuali null
. Untuk menambahkan objek ke daftar, kita perlu jenis wildcard lain - ? super
? super
2. Mengapa saya tidak bisa mendapatkan item dari daftar di bawah ini?
public static <T> T getFirst(List<? super T> list) { return list.get(0);
JawabannyaTidak dapat membaca item dari wadah dengan wildcard
? super
? super
, kecuali objek
Object
kelas
public static <T> Object getFirst(List<? super T> list) { return list.get(0); }
Prinsip Get and Put atau PECS (Produser Memperpanjang Super Konsumen)
Fitur wildcard dengan batas atas dan bawah memberikan fitur tambahan yang terkait dengan penggunaan jenis yang aman. Anda hanya bisa membaca dari satu jenis variabel, hanya menulis ke yang lain (pengecualiannya adalah kemampuan untuk menulis
null
untuk
extends
dan membaca
Object
untuk
super
). Untuk membuatnya lebih mudah diingat ketika menggunakan wildcard yang mana, ada prinsip PECS - Produser Memperluas Konsumen Super.
- Jika kami mendeklarasikan wildcard dengan extends , maka ini adalah produser . Dia hanya "menghasilkan", menyediakan elemen dari wadah, dan dia tidak menerima apa pun.
- Jika kami mengumumkan wildcard dengan super , maka ini adalah konsumen . Dia hanya menerima, tetapi tidak bisa memberikan apa-apa.
Pertimbangkan menggunakan Wildcard dan prinsip PECS menggunakan metode salin di kelas java.util.Collections sebagai contoh.
public static <T> void copy(List<? super T> dest, List<? extends T> src) { β¦ }
Metode ini menyalin elemen dari daftar
src
asli ke daftar tujuan.
src
- dideklarasikan dengan wildcard
? extends
? extends
dan merupakan produser, dan
dest
dinyatakan dengan wildcard
? super
? super
dan merupakan konsumen. Mengingat kovarians dan contravariance wildcard, Anda dapat menyalin elemen dari daftar
ints
ke daftar
nums
:
List<Number> nums = Arrays.<Number>asList(4.1F, 0.2F); List<Integer> ints = Arrays.asList(1,2); Collections.copy(nums, ints);
Jika kita keliru parameter metode salin karena kesalahan dan mencoba untuk menyalin dari daftar
nums
ke daftar
ints
, kompiler tidak akan mengizinkan kita untuk melakukan ini:
Collections.copy(ints, nums);
<?> dan Jenis mentah
Di bawah ini adalah wildcard dengan wildcard tak terbatas. Kami hanya menempatkan
<?>
, Tanpa kata kunci
super
atau
extends
:
static void printCollection(Collection<?> c) {
Bahkan, wildcard "tak terbatas" semacam itu masih terbatas, dari atas.
Collection<?>
Apakah juga wildcard, seperti "
? extends Object
". Catatan bentuk
Collection<?>
setara dengan
Collection<? extends Object>
Collection<? extends Object>
, yang berarti bahwa koleksi dapat berisi objek dari kelas apa saja, karena semua kelas di Jawa mewarisi dari
Object
- sehingga substitusi disebut tidak terbatas.
Jika kita menghilangkan indikasi jenis, misalnya, seperti di sini:
ArrayList arrayList = new ArrayList();
kemudian mereka mengatakan bahwa
ArrayList
adalah tipe
Raw
dari
ArrayList yang diparameterisasi
<T> . Menggunakan tipe Raw, kami kembali ke era generik dan secara sadar meninggalkan semua fitur yang melekat pada tipe parameter.
Jika kita mencoba memanggil metode parameterisasi pada tipe Raw, kompiler akan memberi kita peringatan "Panggilan tidak dicentang". Jika kita mencoba untuk menetapkan referensi ke tipe Raw yang diparameterisasi menjadi suatu tipe, kompiler akan memberikan peringatan "Penandaan yang tidak dicentang". Mengabaikan peringatan ini, seperti yang akan kita lihat nanti, dapat menyebabkan kesalahan selama eksekusi aplikasi kita.
ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings;
Pengambilan wildcard
Sekarang mari kita coba menerapkan metode yang memungkinkan elemen daftar dalam urutan terbalik.
public static void reverse(List<?> list);
Kesalahan kompilasi terjadi karena metode
reverse
mengambil daftar dengan karakter wildcard tak terbatas
<?>
Sebagai argumen.
<?>
Berarti sama dengan
<? extends Object>
<? extends Object>
. Oleh karena itu, sesuai dengan prinsip PECS,
list
adalah
producer
. Dan
producer
hanya menghasilkan elemen. Dan kita dalam
for
loop memanggil metode
set()
, mis. mencoba menulis ke
list
. Jadi kami bersandar pada perlindungan Java, yang tidak memungkinkan kami untuk menetapkan beberapa nilai berdasarkan indeks.
Apa yang harus dilakukan Pola Pengambilan
Wildcard Capture
akan membantu kami. Di sini kita membuat metode
rev
generik. Ini dideklarasikan dengan variabel tipe
T
Metode ini menerima daftar tipe
T
, dan kita dapat membuat satu set.
public static void reverse(List<?> list) { rev(list); } private static <T> void rev(List<T> list) { List<T> tmp = new ArrayList<T>(list); for (int i = 0; i < list.size(); i++) { list.set(i, tmp.get(list.size()-i-1)); } }
Sekarang semuanya akan dikompilasi bersama kami. Penangkapan wildcard ditangkap di sini. Ketika metode
reverse(List<?> list)
disebut
reverse(List<?> list)
, daftar beberapa objek (misalnya, string atau bilangan bulat) dilewatkan sebagai argumen. Jika kita dapat menangkap tipe objek-objek ini dan menetapkannya ke variabel tipe
X
, maka kita dapat menyimpulkan bahwa
T
adalah
X
Anda dapat membaca lebih lanjut tentang
Wildcard Capture
sini dan di
sini .
Kesimpulan
Jika Anda perlu membaca dari wadah, maka gunakan wildcard dengan batas atas "
? extends
". Jika Anda perlu menulis ke wadah, maka gunakan wildcard dengan batas bawah "
? super
". Jangan gunakan wildcard jika Anda perlu merekam dan membaca.
Jangan gunakan jenis
Raw
! Jika argumen tipe tidak didefinisikan, maka gunakan wildcard
<?>
.
Ketik variabel
Ketika kami menuliskan pengidentifikasi dalam kurung sudut, misalnya,
<T>
atau
<E>
saat mendeklarasikan kelas atau metode, kami membuat
variabel tipe . Variabel tipe adalah pengidentifikasi tanpa kualifikasi yang dapat digunakan sebagai tipe di tubuh kelas atau metode. Variabel tipe dapat dibatasi di atas.
public static <T extends Comparable<T>> T max(Collection<T> coll) { T candidate = coll.iterator().next(); for (T elt : coll) { if (candidate.compareTo(elt) < 0) candidate = elt; } return candidate; }
Dalam contoh ini, ekspresi
T extends Comparable<T>
mendefinisikan
T
(variabel tipe) yang dibatasi di atas oleh tipe
Comparable<T>
. Tidak seperti wildcard, variabel tipe hanya dapat dibatasi di atas (hanya
extends
). Tidak bisa menulis
super
. Selain itu, dalam contoh ini,
T
tergantung pada dirinya sendiri, itu disebut
recursive bound
- perbatasan rekursif.
Ini adalah contoh lain dari kelas Enum:
public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable
Di sini, kelas Enum diparameterisasi berdasarkan tipe E, yang merupakan subtipe dari
Enum<E>
.
Banyak batas
Multiple Bounds
- beberapa batasan. Ini ditulis melalui karakter "
&
", yaitu, kita mengatakan bahwa tipe yang diwakili oleh variabel tipe
T
harus dibatasi dari atas oleh kelas
Object
dan antarmuka yang
Comparable
.
<T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
Rekam
Object & Comparable<? super T>
Object & Comparable<? super T>
membentuk tipe persimpangan
Multiple Bounds
. Keterbatasan pertama - dalam hal ini,
Object
- digunakan untuk
erasure
, proses jenis penulisan ulang. Ini dilakukan oleh kompiler pada tahap kompilasi.
Kesimpulan
Variabel tipe hanya dapat dibatasi di atas satu atau beberapa tipe. Dalam kasus beberapa kendala, batas kiri (kendala pertama) digunakan dalam proses penulisan ulang (Penghapusan Jenis).
Ketik penghapusan
Penghapusan Tipe adalah pemetaan tipe (mungkin termasuk tipe parameter dan variabel tipe) ke tipe yang tidak pernah tipe parameter atau tipe variabel. Kami menulis tipe
T
mashing sebagai
|T|
.
Tampilan mashing didefinisikan sebagai berikut:
- Menghancurkan parameterisasi tipe G < T1 , ..., Tn > adalah | G |
- Menghancurkan tipe bersarang TC adalah | T |. C
- Menghancurkan array tipe T [] adalah | T | []
- Menghancurkan variabel tipe sedang menumbuk batas kirinya
- Menghancurkan jenis lainnya adalah jenis ini sendiri
Selama pelaksanaan Penghapusan Jenis (type mashing), kompiler melakukan tindakan berikut:
- menambahkan casting tipe untuk memberikan keamanan tipe jika perlu
- menghasilkan metode Bridge untuk mempertahankan polimorfisme
T (Jenis)
| | T | (Tipe tumbuk)
|
Daftar <Integer>, Daftar <String>, Daftar <Daftar <String >>
| Daftar
|
Daftar <Integer> []
| Daftar []
|
Daftar
| Daftar
|
int
| int
|
Integer
| Integer
|
<T extends Sebanding <T>>
| Sebanding
|
<T memperluas Objek & Sebanding <? super T >>
| Obyek
|
LinkedCollection <E> .Node
| LinkedCollection.Node
|
Tabel ini menunjukkan apa tipe yang berbeda berubah selama proses menumbuk, Type Erasure.
Pada tangkapan layar di bawah ini adalah dua contoh program:

Perbedaan antara keduanya adalah bahwa kesalahan waktu kompilasi terjadi di sebelah kiri, dan di sebelah kanan semuanya dikompilasi tanpa kesalahan. Mengapa
JawabannyaDi Jawa, dua metode berbeda tidak dapat memiliki tanda tangan yang sama. Dalam proses Penghapusan Jenis, kompiler akan menambahkan metode jembatan
public int compareTo(Object o)
. Tetapi kelas sudah berisi metode dengan tanda tangan sedemikian rupa sehingga akan menyebabkan kesalahan saat kompilasi.
Kompilasi kelas Name dengan menghapus metode
compareTo(Object o)
dan lihat bytecode yang dihasilkan menggunakan javap:
# javap Name.class Compiled from "Name.java" public class ru.sberbank.training.generics.Name implements java.lang.Comparable<ru.sberbank.training.generics.Name> { public ru.sberbank.training.generics.Name(java.lang.String); public java.lang.String toString(); public int compareTo(ru.sberbank.training.generics.Name); public int compareTo(java.lang.Object); }
Kami melihat bahwa kelas tersebut berisi metode
int compareTo(java.lang.Object)
, meskipun kami menghapusnya dari kode sumber. Ini adalah metode jembatan yang ditambahkan oleh kompiler.
Jenis yang dapat diverifikasi
Di Jawa, kami mengatakan bahwa suatu jenis dapat
reifiable
jika informasinya sepenuhnya dapat diakses pada saat dijalankan. Jenis yang dapat diverifikasi meliputi:
- Jenis primitif ( int , panjang , boolean )
- Tipe-tipe yang tidakparameter (non-generik) ( String , Integer )
- Tipe parameter yang parameternya direpresentasikan sebagai wildcard tak terbatas (karakter wildcard tak terbatas) ( Daftar <?> , Koleksi <?> )
- Jenis mentah (tidak berbentuk) ( Daftar , ArrayList )
- Array yang komponennya adalah jenis yang dapat diverifikasi ( int [] , Nomor [] , Daftar <?> [] , Daftar [ )
Mengapa informasi tentang beberapa jenis tersedia tetapi tidak tentang yang lain? Faktanya adalah bahwa karena proses menimpa jenis oleh kompiler, informasi tentang beberapa jenis mungkin hilang. Jika hilang, maka jenis ini tidak lagi dapat diverifikasi. Artinya, itu tidak tersedia saat runtime. Jika tersedia - masing-masing, dapat diverifikasi.
Keputusan untuk tidak menyediakan semua tipe generik pada saat run time adalah salah satu keputusan desain yang paling penting dan saling bertentangan dalam sistem tipe Java. Ini dilakukan, pertama-tama, untuk kompatibilitas dengan kode yang ada. Saya harus membayar untuk kompatibilitas migrasi - aksesibilitas penuh dari sistem tipe generik pada saat run time tidak dimungkinkan.
Jenis apa yang tidak dapat diverifikasi:
- Jenis variabel ( T )
- Jenis parameter dengan tipe parameter yang ditentukan ( Daftar <Number> ArrayList <String> , Daftar <List <String>> )
- Tipe parameter dengan batas atas atau bawah yang ditentukan ( Daftar <? Perluas Nomor>, Sebandingi <? Super String> ). Tapi di sini ada reservasi: Daftar <? extends Object> - tidak dapat diverifikasi, tetapi List <?> - reifiable
Dan satu tugas lagi. Mengapa dalam contoh di bawah ini tidak dapat membuat Pengecualian berparameter?
class MyException<T> extends Exception { T t; }
JawabannyaSetiap ekspresi catch dalam try-catch memeriksa tipe pengecualian yang diterima selama eksekusi program (yang setara dengan instanceof), masing-masing, tipe tersebut harus dapat diverifikasi. Oleh karena itu, Throwable dan subtipe-nya tidak dapat diparameterisasi.
class MyException<T> extends Exception {
Peringatan yang tidak dicentang
Mengkompilasi aplikasi kita dapat menghasilkan apa yang disebut
Unchecked Warning
- peringatan bahwa kompiler tidak dapat dengan benar menentukan tingkat keamanan penggunaan tipe kita. Ini bukan kesalahan, tapi peringatan, jadi Anda bisa melewatinya. Tetapi disarankan untuk memperbaiki semuanya agar terhindar dari masalah di masa depan.
Menumpuk polusi
Seperti yang kami sebutkan sebelumnya, menetapkan referensi ke tipe Raw ke variabel dari tipe parameter menyebabkan peringatan "Tidak ditandai tugas". Jika kita mengabaikannya, sebuah situasi yang disebut "
Heap Pollution
" (heap polusi) mungkin terjadi. Berikut ini sebuah contoh:
static List<String> t() { List l = new ArrayList<Number>(); l.add(1); List<String> ls = l;
Pada baris (1), kompiler memperingatkan "Tugas tidak dicentang."
Kita perlu memberikan contoh lain dari "tumpukan polusi" - ketika kita menggunakan objek parameterisasi. Cuplikan kode di bawah ini dengan jelas menunjukkan bahwa tidak diperbolehkan menggunakan tipe parameter sebagai argumen untuk metode yang menggunakan
Varargs
. Dalam kasus ini, parameter metode m adalah
List<String>β¦
, mis. sebenarnya, sebuah array elemen dari tipe
List<String>
. Diberi aturan menampilkan tipe selama mashing, tipe
stringLists
berubah menjadi array daftar mentah (
List[]
), mis. tugas dapat dilakukan
Object[] array = stringLists;
dan kemudian menulis ke
array
objek selain daftar string (1), yang akan
ClassCastException
dalam string (2).
static void m(List<String>... stringLists) { Object[] array = stringLists; List<Integer> tmpList = Arrays.asList(42); array[0] = tmpList;
Pertimbangkan contoh lain:
ArrayList<String> strings = new ArrayList<>(); ArrayList arrayList = new ArrayList(); arrayList = strings;
Java mengizinkan penugasan pada baris (1). Ini diperlukan untuk kompatibilitas ke belakang. Tetapi jika kita mencoba menjalankan metode
add
pada baris (2), kita mendapatkan peringatan
Unchecked call
- kompiler memperingatkan kita akan kemungkinan kesalahan. Faktanya, kami mencoba menambahkan integer ke daftar string.
Refleksi
Meskipun, selama kompilasi, tipe parameterisasi menjalani prosedur tipe erasure, kita bisa mendapatkan beberapa informasi menggunakan Reflection.
- Semua dapat diverifikasi tersedia melalui mekanisme Refleksi.
- Informasi tentang jenis bidang kelas, parameter metode, dan nilai yang dikembalikan oleh mereka tersedia melalui Refleksi.
Jika kami ingin mendapatkan informasi tentang jenis objek melalui Refleksi dan jenis ini tidak Reifiable
, maka kami tidak akan berhasil. Tetapi, jika, misalnya, objek ini dikembalikan kepada kami dengan beberapa metode, maka kami bisa mendapatkan jenis nilai yang dikembalikan oleh metode ini: java.lang.reflect.Method.getGenericReturnType()
Dengan munculnya Generics, kelas telah java.lang.Class
menjadi parameter. Pertimbangkan kode ini: List<Integer> ints = new ArrayList<Integer>(); Class<? extends List> k = ints.getClass(); assert k == ArrayList.class;
Variabelnya ints
adalah tipe List<Integer>
dan berisi referensi ke objek tipe ArrayList< Integer>
. Maka itu ints.getClass()
akan mengembalikan objek bertipe Class<ArrayLis>
, karena List<Integer>
ditimpa dalam List
. Class<ArrayList>
Bisakah objek tipe ditugaskan ke variabel k
tipe Class<? extends List>
sesuai dengan kovarian karakter wildcard? extends
. A ArrayList.class
mengembalikan objek bertipe Class<ArrayList>
.Kesimpulan
, Reifiable. Reifiable : , , , Raw , reifiable.
Unchecked Warnings Β« Β» .
Reflection , Reifiable. Reflection , .
Type Inference
Β« Β». () . :
List<Integer> list = new ArrayList<Integer>();
- Java 7
ArrayList
:
List<Integer> list = new ArrayList<>();
ArrayList
β
List<Integer>
.
type inference
.
Java 8 JEP 101.
Type Inference. :
- (reduction)
- (incorporation)
- (resolution)
: , , β .
, . JEP 101 .
, :
class List<E> { static <Z> List<Z> nil() { ... }; static <Z> List<Z> cons(Z head, List<Z> tail) { ... }; E head() { ... } }
List.nil()
:
List<String> ls = List.nil();
,
List.nil()
String
β JDK 7, .
, , , :
List.cons(42, List.nil());
JDK 7 compile-time error. JDK 8 . JEP-101, β . JDK 8 β :
List.cons(42, List.<Integer>nil());
JEP-101 , , :
String s = List.nil().head();
, . , JDK , :
String s = List.<String>nil().head();
JEP 101 StackOverflow . , , 7- , 8- β ? :
class Test { static void m(Object o) { System.out.println("one"); } static void m(String[] o) { System.out.println("two"); } static <T> T g() { return null; } public static void main(String[] args) { m(g()); } }
- JDK1.8:
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: invokestatic #6
0
g:()Ljava/lang/Object;
java.lang.Object
. , 3 («») ,
java.lang.String
, 6
m:([Ljava/lang/String;)
, Β«twoΒ».
- JDK1.7 β Java 7:
public static void main(java.lang.String[]); flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=1, args_size=1 0: invokestatic #6
,
checkcast
, Java 8,
m:(Ljava/lang/Object;)
, Β«oneΒ».
Checkcast
β , Java 8.
, Oracle
JDK1.7 JDK 1.8 , Java, , .
, Java 8 , Java 7, :
public static void main(String[] args) { m((Object)g()); }
Kesimpulan
Java Generics . , :
- Bloch, Joshua. Effective Java. Third Edition. Addison-Wesley. ISBN-13: 978-0-13-468599-1
, Java Generics.