CodeGym /Java Blog /Acak /Generik Java: cara menggunakan tanda kurung siku dalam pr...
John Squirrels
Level 41
San Francisco

Generik Java: cara menggunakan tanda kurung siku dalam praktik

Dipublikasikan di grup Acak

Perkenalan

Dimulai dengan JSE 5.0, generik ditambahkan ke gudang bahasa Java.

Apa itu generik di java?

Generik adalah mekanisme khusus Java untuk mengimplementasikan pemrograman generik — cara untuk mendeskripsikan data dan algoritme yang memungkinkan Anda bekerja dengan tipe data yang berbeda tanpa mengubah deskripsi algoritme. Situs web Oracle memiliki tutorial terpisah yang didedikasikan untuk obat generik: " Pelajaran ". Untuk memahami obat generik, pertama-tama Anda harus mencari tahu mengapa mereka dibutuhkan dan apa yang mereka berikan. Bagian " Mengapa Menggunakan Generik? " dari tutorial mengatakan bahwa beberapa tujuan adalah pengecekan tipe yang lebih kuat pada waktu kompilasi dan penghapusan kebutuhan akan gips eksplisit. Generik di Java: cara menggunakan tanda kurung siku dalam praktik - 1Mari bersiap untuk beberapa pengujian di kompiler java online Tutorialspoint kesayangan kita . Misalkan Anda memiliki kode berikut:

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. Tetapi bagaimana jika bos mendatangi kita dan mengatakan bahwa "Halo, dunia!" adalah frasa yang terlalu sering digunakan dan Anda harus mengembalikan hanya "Halo"? Kami akan menghapus kode yang menggabungkan ", world!" Ini tampaknya cukup tidak berbahaya, bukan? Tapi kami benar-benar mendapatkan kesalahan PADA WAKTU KOMPIL:

error: incompatible types: Object cannot be converted to String
Masalahnya adalah bahwa dalam Daftar kami menyimpan Objek. String adalah turunan dari Object (karena semua kelas Java secara implisit mewarisi Object ), yang berarti kita memerlukan pemeran eksplisit, tetapi kita tidak menambahkannya. Selama operasi penggabungan, metode String.valueOf(obj) statis akan dipanggil menggunakan objek. Akhirnya, ini akan memanggil metode toString kelas Object . Dengan kata lain, List kita berisi Object . Ini berarti bahwa dimanapun kita membutuhkan tipe tertentu (bukan Object ), kita harus melakukan konversi tipe itu 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 List mengambil objek, List tidak hanya dapat menyimpan String , tetapi juga Integer . Tetapi yang terburuk adalah kompiler tidak melihat ada yang salah di sini. Dan sekarang kita akan mendapatkan error AT RUN TIME (dikenal sebagai "runtime error"). Kesalahannya adalah:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Anda harus setuju bahwa ini sangat tidak baik. Dan semua ini karena kompiler bukanlah kecerdasan buatan yang selalu mampu menebak maksud programmer dengan benar. Java SE 5 memperkenalkan generik untuk memberi tahu kompiler tentang niat kami - tentang jenis apa yang akan kami gunakan. Kami memperbaiki kode kami dengan memberi tahu kompiler apa yang kami 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 Anda lihat, kita tidak lagi memerlukan gips ke String . Selain itu, kami memiliki tanda kurung sudut yang mengelilingi argumen tipe. Sekarang kompiler tidak mengizinkan kita mengkompilasi kelas sampai kita menghapus baris yang menambahkan 123 ke daftar, karena ini adalah Integer . Dan itu akan memberitahu kita begitu. Banyak orang menyebut obat generik "gula sintaksis". Dan mereka benar, karena setelah generik dikompilasi, mereka benar-benar menjadi konversi tipe yang sama. Mari kita lihat bytecode dari kelas yang dikompilasi: yang menggunakan cast eksplisit dan yang menggunakan generik: Generik di Jawa: cara menggunakan tanda kurung siku dalam praktik - 2Setelah kompilasi, semua generik dihapus. Ini disebut " penghapusan tipe". Penghapusan tipe dan generik dirancang agar kompatibel dengan versi lama JDK sementara secara bersamaan memungkinkan kompiler untuk membantu dengan definisi tipe di versi baru Java.

Jenis mentah

Berbicara tentang obat generik, kami selalu memiliki dua kategori: tipe berparameter dan tipe mentah. Tipe mentah adalah tipe yang menghilangkan "klarifikasi tipe" dalam kurung sudut: Generik di Jawa: cara menggunakan tanda kurung siku dalam praktik - 3Tipe parameter, di sisi lain, menyertakan "klarifikasi": Generik di Java: cara menggunakan tanda kurung siku dalam praktik - 4Seperti yang Anda lihat, kami menggunakan konstruksi yang tidak biasa, ditandai dengan panah di tangkapan layar. Ini adalah sintaks khusus yang ditambahkan ke Java SE 7. Ini disebut " berlian ". Mengapa? Kurung sudut membentuk wajik: <> . Anda juga harus tahu bahwa sintaks berlian dikaitkan dengan konsep " tipe inferensi ". Lagi pula, kompiler, melihat <>di sebelah kanan, lihat sisi kiri operator penugasan, di mana ia menemukan jenis variabel yang nilainya ditugaskan. Berdasarkan apa yang ditemukannya di bagian ini, ia memahami jenis nilai di sebelah kanan. Faktanya, jika tipe generik diberikan di sebelah kiri, tetapi tidak di sebelah kanan, kompiler 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);
	}
}
Tapi ini memadukan gaya baru dengan obat generik dan gaya lama tanpanya. Dan ini sangat tidak diinginkan. Saat mengkompilasi kode di atas, kami mendapatkan pesan berikut:

Note: HelloWorld.java uses unchecked or unsafe operations
Nyatanya, alasan mengapa Anda bahkan perlu menambahkan berlian di sini sepertinya tidak bisa dipahami. Tapi inilah 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);
	}
}
Anda akan ingat bahwa ArrayList memiliki konstruktor kedua yang menggunakan koleksi sebagai argumen. Dan di sinilah tersembunyi sesuatu yang menyeramkan. Tanpa sintaks berlian, kompiler tidak mengerti bahwa ia sedang ditipu. Dengan sintaks berlian, itu benar. Jadi, Aturan #1 adalah: selalu gunakan sintaks berlian dengan tipe berparameter. Jika tidak, kami berisiko kehilangan tempat kami menggunakan tipe mentah. Untuk menghilangkan peringatan "uses unchecked or unsafe operations", kita dapat menggunakan anotasi @SuppressWarnings("unchecked") pada metode atau kelas. Tetapi pikirkan mengapa Anda memutuskan untuk menggunakannya. Ingat aturan nomor satu. Mungkin Anda perlu menambahkan argumen tipe.

Metode Java Generik

Generik memungkinkan Anda membuat metode yang tipe parameter dan tipe pengembaliannya diparameterisasi. Bagian terpisah dikhususkan untuk kemampuan ini dalam tutorial Oracle: " Metode Generik ". Sangat penting untuk mengingat sintaks yang diajarkan dalam tutorial ini:
  • itu termasuk daftar parameter tipe di dalam kurung sudut;
  • daftar parameter tipe berjalan sebelum tipe pengembalian metode.
Mari kita lihat sebuah contoh:

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 , Anda akan melihat bahwa ia memiliki dua metode generik. Berkat kemungkinan inferensi tipe, kita dapat menunjukkan tipe secara langsung ke kompiler, atau kita dapat menentukannya sendiri. Kedua opsi disajikan dalam contoh. Omong-omong, sintaksnya sangat masuk akal jika Anda memikirkannya. Saat mendeklarasikan metode generik, kami menentukan parameter tipe SEBELUM metode, karena jika kami mendeklarasikan parameter tipe setelah metode, JVM tidak akan dapat mengetahui tipe mana yang digunakan. Oleh karena itu, pertama-tama kita mendeklarasikan bahwa kita akan menggunakan parameter tipe T , dan kemudian kita mengatakan bahwa kita akan mengembalikan tipe ini. Secara alami, Util.<Integer>getValue(element, String.class) akan gagal dengan kesalahan:tipe yang tidak kompatibel: Class<String> tidak dapat dikonversi ke Class<Integer> . Saat menggunakan metode generik, Anda harus selalu mengingat 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 berjalan dengan baik. Tetapi hanya selama kompiler memahami bahwa tipe kembalian dari metode yang dipanggil adalah Integer . Ganti pernyataan keluaran konsol dengan baris berikut:

System.out.println(Util.getValue(element) + 1);
Kami mendapatkan kesalahan:

bad operand types for binary operator '+', first type: Object, second type: int.
Dengan kata lain, telah terjadi penghapusan tipe. Kompiler melihat bahwa tidak ada yang menentukan tipenya, jadi tipenya diindikasikan sebagai Objek dan metodenya gagal dengan kesalahan.

Kelas generik

Tidak hanya metode yang dapat diparameterisasi. Kelas juga bisa. Bagian "Jenis Generik" dari tutorial Oracle dikhususkan untuk ini. Mari kita pertimbangkan 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 kelas generik, parameter tipe ditunjukkan setelah nama kelas. Sekarang mari buat instance dari kelas ini di metode utama :

public static void main(String []args) {
	SomeType<String> st = new SomeType<>();
	List<String> list = Arrays.asList("test");
	st.test(list);
}
Kode ini akan berjalan dengan baik. Compiler melihat bahwa ada List of numbers dan Collection of Strings . Tapi bagaimana jika kita menghilangkan parameter type dan melakukan ini:

SomeType st = new SomeType();
List<String> list = Arrays.asList("test");
st.test(list);
Kami mendapatkan kesalahan:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer
Sekali lagi, ini adalah penghapusan tipe. Karena kelas tidak lagi menggunakan parameter tipe, kompiler memutuskan bahwa, karena kami meneruskan List , metode dengan List<Integer> adalah yang paling tepat. Dan kami gagal karena kesalahan. Oleh karena itu, kami memiliki Aturan #2: Jika Anda memiliki kelas generik, selalu tentukan parameter tipenya.

Batasan

Kami dapat membatasi jenis yang ditentukan dalam metode dan kelas generik. Sebagai contoh, misalkan kita ingin sebuah container menerima hanya sebuah Number sebagai argumen tipe. Fitur ini dijelaskan di bagian Bounded Type Parameters dari tutorial Oracle. 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 parameter tipe ke kelas/antarmuka Angka atau turunannya. Perhatikan bahwa Anda tidak hanya dapat menentukan kelas, tetapi juga antarmuka. Misalnya:

public static class NumberContainer<T extends Number & Comparable> {
Generik juga mendukung wildcard Mereka dibagi menjadi tiga jenis: Penggunaan wildcard Anda harus mematuhi prinsip Get-Put . Itu dapat diungkapkan sebagai berikut:
  • Gunakan perpanjangan wildcard saat Anda hanya mendapatkan nilai dari struktur.
  • Gunakan super wildcard saat Anda hanya memasukkan nilai ke dalam struktur.
  • Dan jangan gunakan wildcard saat Anda berdua ingin mendapatkan dan meletakkan dari/ke struktur.
Prinsip ini disebut juga dengan prinsip Producer Extends Consumer Super (PECS). Berikut adalah contoh kecil dari kode sumber untuk metode Collections.copy Java : Generik di Jawa: cara menggunakan tanda kurung siku dalam praktik - 5Dan inilah sedikit contoh dari apa yang TIDAK AKAN berfungsi:

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);
}
Tetapi jika Anda mengganti extends dengan super , maka semuanya baik-baik saja. Karena kita mengisi daftar dengan nilai sebelum menampilkan isinya, itu adalah consumer . Karenanya, kami menggunakan super.

Warisan

Obat generik memiliki fitur menarik lainnya: pewarisan. Cara kerja warisan untuk obat generik dijelaskan di bawah " Generics, Inheritance, and Subtypes " di tutorial Oracle. Yang penting adalah mengingat dan mengenali hal-hal berikut. Kami tidak dapat melakukan ini:

List<CharSequence> list1 = new ArrayList<String>();
Karena warisan bekerja secara berbeda dengan obat generik: Generik di Java: cara menggunakan tanda kurung siku dalam praktik - 6Dan inilah contoh bagus lainnya yang akan gagal karena kesalahan:

List<String> list1 = new ArrayList<>();
List<Object> list2 = list1;
Sekali lagi, semuanya sederhana di sini. List<String> bukan turunan dari List<Object> , meskipun String adalah turunan dari Object . Untuk memperkuat apa yang Anda pelajari, kami sarankan Anda menonton video pelajaran dari Kursus Java kami

Kesimpulan

Jadi kami menyegarkan ingatan kami tentang obat generik. Jika Anda jarang memanfaatkan sepenuhnya kemampuannya, beberapa detail menjadi tidak jelas. Saya harap ulasan singkat ini membantu membangkitkan ingatan Anda. Untuk hasil yang lebih baik lagi, saya sangat menyarankan agar Anda membiasakan diri dengan materi berikut:
Komentar
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION