Perkenalan
Dimulai dengan BEJ 5.0, obat generik ditambahkan ke gudang bahasa Java.Apa obat generik di Java?
Generik (generalisasi) adalah sarana khusus bahasa Java untuk mengimplementasikan pemrograman umum: pendekatan khusus untuk mendeskripsikan data dan algoritme yang memungkinkan Anda bekerja dengan berbagai jenis data tanpa mengubah deskripsinya. Di situs web Oracle, tutorial terpisah didedikasikan untuk obat generik: “ Pelajaran: Generik ”.
import java.util.*;
public class HelloWorld{
public static void main(String []args){
List list = new ArrayList();
list.add("Hello");
String text = list.get(0) + ", world!";
System.out.print(text);
}
}
Kode ini akan berjalan dengan baik. Namun bagaimana jika mereka mendatangi kita dan mengucapkan kalimat “Halo, dunia!” dikalahkan dan kamu hanya bisa kembali Halo? Mari kita hapus rangkaian dengan string dari kode ", world!"
. Tampaknya, apa yang lebih tidak berbahaya? Namun kenyataannya, kita akan menerima kesalahan SELAMA KOMPILASI : error: incompatible types: Object cannot be converted to String
Masalahnya adalah dalam kasus kita Daftar menyimpan daftar objek bertipe Objek. Karena String adalah turunan dari Object (karena semua kelas secara implisit diwarisi dari Object di Java), maka memerlukan pemeran eksplisit, yang tidak kami lakukan. Dan ketika menggabungkan, metode statis String.valueOf(obj) akan dipanggil pada objek, yang pada akhirnya akan memanggil metode toString pada Objek. Artinya, Daftar kami berisi Objek. Ternyata ketika kita membutuhkan tipe tertentu, dan bukan Object, kita harus melakukan casting tipe sendiri:
import java.util.*;
public class HelloWorld{
public static void main(String []args){
List list = new ArrayList();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println((String)str);
}
}
}
Namun, dalam kasus ini, karena Daftar menerima daftar objek, ia tidak hanya menyimpan String, tetapi juga Integer. Namun yang terburuk adalah dalam hal ini kompiler tidak akan melihat ada yang salah. Dan di sini kita akan menerima kesalahan SELAMA EKSEKUSI (mereka juga mengatakan bahwa kesalahan itu diterima "saat Runtime"). Kesalahannya adalah: java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Setuju, bukan yang paling menyenangkan. Dan semua ini karena kompiler bukanlah kecerdasan buatan dan tidak dapat menebak semua yang dimaksud oleh pemrogram. Untuk memberi tahu kompiler lebih banyak tentang tipe apa yang akan kita gunakan, Java SE 5 memperkenalkan obat generik . Mari kita perbaiki versi kita dengan memberi tahu compiler apa yang kita inginkan:
import java.util.*;
public class HelloWorld {
public static void main(String []args){
List<String> list = new ArrayList<>();
list.add("Hello!");
list.add(123);
for (Object str : list) {
System.out.println(str);
}
}
}
Seperti yang bisa kita lihat, kita tidak lagi memerlukan cast ke String. Selain itu, kami sekarang memiliki tanda kurung sudut yang membingkai obat generik. Sekarang kompiler tidak akan mengizinkan kelas untuk dikompilasi sampai kita menghapus penambahan 123 ke dalam daftar, karena ini adalah bilangan bulat. Dia akan memberitahu kita demikian. Banyak orang menyebut obat generik sebagai "gula sintaksis". Dan mereka benar, karena obat generik memang akan menjadi kasta yang sama ketika dikompilasi. Mari kita lihat bytecode dari kelas yang dikompilasi: dengan casting manual dan menggunakan obat generik:
Tipe Mentah atau tipe mentah
Ketika berbicara tentang obat generik, kami selalu memiliki dua kategori: tipe yang diketik (Jenis Generik) dan tipe “mentah” (Tipe Mentah). Tipe mentah adalah tipe tanpa menentukan “kualifikasi” dalam tanda kurung siku:<>
Sintaks wajik juga diasosiasikan dengan konsep " Type Inference ", atau inferensi tipe. Lagi pula, kompiler, melihat <> di sebelah kanan, melihat ke sisi kiri, di mana deklarasi tipe variabel yang diberi nilai berada. Dan dari bagian ini dia memahami tipe apa yang diketikkan nilai di sebelah kanan. Faktanya, jika generik ditentukan di sisi kiri dan tidak ditentukan di sisi kanan, kompiler akan dapat menyimpulkan tipenya:
import java.util.*;
public class HelloWorld{
public static void main(String []args) {
List<String> list = new ArrayList();
list.add("Hello World");
String data = list.get(0);
System.out.println(data);
}
}
Namun, ini akan menjadi campuran gaya baru dengan obat generik dan gaya lama tanpa obat generik. Dan ini sangat tidak diinginkan. Saat mengkompilasi kode di atas kita akan menerima pesan: Note: HelloWorld.java uses unchecked or unsafe operations
. Faktanya, sepertinya tidak jelas mengapa kita perlu menambahkan berlian di sini. Tapi ini contohnya:
import java.util.*;
public class HelloWorld{
public static void main(String []args) {
List<String> list = Arrays.asList("Hello", "World");
List<Integer> data = new ArrayList(list);
Integer intNumber = data.get(0);
System.out.println(data);
}
}
Seperti yang kita ingat, ArrayList juga memiliki konstruktor kedua yang mengambil koleksi sebagai masukan. Dan disinilah letak kebohongannya. Tanpa sintaks diamond, kompiler tidak memahami bahwa ia sedang ditipu, tetapi dengan diamond ia memahaminya. Oleh karena itu, aturan #1 : selalu gunakan sintaks berlian jika kita menggunakan tipe yang diketik. Jika tidak, kami berisiko kehilangan tempat kami menggunakan tipe mentah. Untuk menghindari peringatan di log yang “menggunakan operasi yang tidak dicentang atau tidak aman” Anda dapat menentukan anotasi khusus pada metode atau kelas yang digunakan: @SuppressWarnings("unchecked")
Suppress diterjemahkan sebagai penekan, yang secara harfiah berarti menyembunyikan peringatan. Namun pikirkan mengapa Anda memutuskan untuk menunjukkannya? Ingat aturan nomor satu dan mungkin Anda perlu menambahkan pengetikan.
Metode Generik
Generik memungkinkan Anda mengetik metode. Ada bagian terpisah yang didedikasikan untuk fitur ini dalam tutorial Oracle: “ Metode Generik ”. Dari tutorial ini, penting untuk mengingat sintaksnya:- menyertakan daftar parameter yang diketik di dalam tanda kurung sudut;
- daftar parameter yang diketik diletakkan sebelum metode yang dikembalikan.
import java.util.*;
public class HelloWorld{
public static class Util {
public static <T> T getValue(Object obj, Class<T> clazz) {
return (T) obj;
}
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList("Author", "Book");
for (Object element : list) {
String data = Util.getValue(element, String.class);
System.out.println(data);
System.out.println(Util.<String>getValue(element));
}
}
}
Jika Anda melihat kelas Util, kita melihat dua metode yang diketik di dalamnya. Dengan inferensi tipe, kita bisa memberikan definisi tipe langsung ke compiler, atau kita bisa menentukannya sendiri. Kedua opsi disajikan dalam contoh. Omong-omong, sintaksnya cukup logis jika Anda memikirkannya. Saat mengetik suatu metode, kita menentukan metode generik SEBELUM metode tersebut karena jika kita menggunakan metode generik setelah metode tersebut, Java tidak akan dapat mengetahui tipe mana yang akan digunakan. Oleh karena itu, pertama-tama kami mengumumkan bahwa kami akan menggunakan T generik, dan kemudian kami mengatakan bahwa kami akan mengembalikan generik ini. Secara alami, Util.<Integer>getValue(element, String.class)
itu akan gagal karena kesalahan incompatible types: Class<String> cannot be converted to Class<Integer>
. Saat menggunakan metode yang diketik, Anda harus selalu ingat tentang penghapusan tipe. Mari kita lihat sebuah contoh:
import java.util.*;
public class HelloWorld {
public static class Util {
public static <T> T getValue(Object obj) {
return (T) obj;
}
}
public static void main(String []args) {
List list = Arrays.asList(2, 3);
for (Object element : list) {
System.out.println(Util.<Integer>getValue(element) + 1);
}
}
}
Ini akan bekerja dengan baik. Tetapi hanya selama kompiler memahami bahwa metode yang dipanggil memiliki tipe Integer. Mari kita ganti keluaran konsol dengan baris berikut: System.out.println(Util.getValue(element) + 1);
Dan kita mendapatkan kesalahan: tipe operan buruk untuk operator biner '+', tipe pertama: Objek, tipe kedua: int Artinya, tipe telah dihapus. Kompiler melihat bahwa tidak ada yang menentukan tipenya, tipenya ditentukan sebagai Objek dan eksekusi kode gagal karena kesalahan.
Tipe Generik
Anda tidak hanya dapat mengetikkan metode, tetapi juga kelas itu sendiri. Oracle memiliki bagian “ Jenis Generik ” yang didedikasikan untuk ini dalam panduan mereka. Mari kita lihat sebuah contoh:public static class SomeType<T> {
public <E> void test(Collection<E> collection) {
for (E element : collection) {
System.out.println(element);
}
}
public void test(List<Integer> collection) {
for (Integer element : collection) {
System.out.println(element);
}
}
}
Semuanya sederhana di sini. Jika kita menggunakan suatu kelas, generiknya dicantumkan setelah nama kelas. Sekarang mari kita buat sebuah instance dari kelas ini dalam metode utama:
public static void main(String []args) {
SomeType<String> st = new SomeType<>();
List<String> list = Arrays.asList("test");
st.test(list);
}
Ini akan bekerja dengan baik. Kompiler melihat bahwa ada Daftar angka dan Koleksi bertipe String. Namun bagaimana jika kita menghapus obat generiknya dan melakukan ini:
SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Kami akan mendapatkan kesalahan: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Ketik penghapusan lagi. Karena kelas tidak lagi memiliki generik, kompiler memutuskan bahwa sejak kita meneruskan Daftar, metode dengan Daftar<Integer> lebih tepat. Dan kita terjatuh karena sebuah kesalahan. Oleh karena itu, aturan #2: Jika suatu kelas diketik, selalu tentukan tipenya dalam generik .
Pembatasan
Kita dapat menerapkan pembatasan pada tipe yang ditentukan dalam obat generik. Misalnya, kita ingin container hanya menerima Number sebagai input. Fitur ini dijelaskan dalam Tutorial Oracle di bagian Parameter Tipe Terikat . Mari kita lihat sebuah contoh:import java.util.*;
public class HelloWorld{
public static class NumberContainer<T extends Number> {
private T number;
public NumberContainer(T number) { this.number = number; }
public void print() {
System.out.println(number);
}
}
public static void main(String []args) {
NumberContainer number1 = new NumberContainer(2L);
NumberContainer number2 = new NumberContainer(1);
NumberContainer number3 = new NumberContainer("f");
}
}
Seperti yang Anda lihat, kami telah membatasi tipe generik menjadi kelas/antarmuka Number dan turunannya. Menariknya, Anda tidak hanya dapat menentukan kelas, tetapi juga antarmuka. Misalnya: public static class NumberContainer<T extends Number & Comparable> {
Generik juga memiliki konsep Wildcard https://docs.Oracle.com/javase/tutorial/java/generics/wildcards.html Mereka, pada gilirannya, dibagi menjadi tiga jenis:
- Wildcard Batas Atas - < ? memperluas Nomor >
- Wildcard Tak Terbatas - < ? >
- Wildcard Batas Bawah - < ? bilangan bulat super >
public static class TestClass {
public static void print(List<? extends String> list) {
list.add("Hello World!");
System.out.println(list.get(0));
}
}
public static void main(String []args) {
List<String> list = new ArrayList<>();
TestClass.print(list);
}
Namun jika Anda mengganti extends dengan super, semuanya akan baik-baik saja. Karena kita mengisi daftar dengan nilai sebelum mengeluarkannya, bagi kita itu adalah konsumen, yaitu konsumen. Oleh karena itu, kami menggunakan super.
Warisan
Ada ciri lain yang tidak biasa dari obat generik - warisannya. Pewarisan obat generik dijelaskan dalam tutorial Oracle di bagian " Generik, Warisan, dan Subtipe ". Yang utama adalah mengingat dan menyadari hal-hal berikut. Kami tidak dapat melakukan ini:List<CharSequence> list1 = new ArrayList<String>();
Karena warisan bekerja secara berbeda dengan obat generik:
List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Semuanya juga sederhana di sini. List<String> bukan merupakan turunan dari Daftar<Objek>, meskipun String merupakan turunan dari Object.
Terakhir
Jadi kami menyegarkan ingatan kami tentang obat generik. Jika mereka jarang digunakan dengan sekuat tenaga, beberapa detail akan hilang dari ingatan. Semoga ulasan singkat ini membantu menyegarkan ingatan Anda. Dan untuk hasil yang lebih baik, saya sangat menyarankan Anda membaca materi berikut ini:- Yuri Tkach: Tipe mentah - Generik #1 - Java Tingkat Lanjut
- Warisan dan ekstender generik - Generik #2 - Java Tingkat Lanjut
- Ekstensi Tipe Rekursif - Generik #3 - Java Tingkat Lanjut
- Alexander Matorin - Generik yang Tidak Jelas
- Pengantar Jawa. Generik. Karakter pengganti | aliran teknologi
- O'Reilly: Generik dan Koleksi Java
GO TO FULL VERSION