Menu

Generics Java: Class dan Method yang Type-Safe

Apa itu generics di Java, cara menulis generic class dan generic method, bounded type parameter, wildcard, dan kenapa type erasure itu penting.

Halaman ini dilengkapi editor yang bisa dijalankan. Edit, jalankan, dan lihat output-nya seketika.

Kenapa Generics Ada

Tipe generic memungkinkanmu menulis kode sekali dan memakainya ulang untuk banyak tipe tanpa mengorbankan type safety. Daripada menulis IntBox, StringBox, dan UserBox secara terpisah, kamu menulis satu Box<T>, dengan T sebagai placeholder yang diisi oleh pemanggilnya.

Kamu sebenarnya sudah memakai generics setiap kali menulis ArrayList<String> atau HashMap seperti HashMap<String, Integer>. Bagian <...> itu adalah argumen tipe. Halaman ini menunjukkan cara menulis generics milikmu sendiri.

Alternatifnya, menyimpan semuanya sebagai Object, membuang informasi tipe dan memaksamu melakukan cast yang jelek dan rawan kesalahan:

Cast di baris terakhir itu melempar ClassCastException saat runtime, jenis exception yang memang dirancang untuk dibuat mustahil oleh generics.

Generic Class

Deklarasikan type parameter di dalam kurung sudut setelah nama class. Menurut konvensi, namanya berupa satu huruf besar: T untuk "type", E untuk "element", K/V untuk key/value.

Di dalam Box, setiap T menjadi tipe apa pun yang diberikan pemanggilnya. Box<String> adalah kotak yang hanya menyimpan dan mengembalikan String. Compiler menolak name.set(99) bahkan sebelum programnya berjalan.

Tanda <> kosong di sebelah kanan (disebut diamond operator) membuat compiler menyimpulkan argumen tipe dari sisi kiri, jadi kamu tidak perlu menulis <String> dua kali.

Generic Method

Satu method bisa punya type parameter sendiri, terlepas dari class-nya. Taruh parameter <T> sebelum tipe return:

Kamu tidak pernah mengirim T secara eksplisit, karena compiler menyimpulkannya dari argumen. Generic method adalah cara utilitas seperti Collections.sort atau List.of tetap type-safe untuk tipe elemen apa pun.

Bounded Type Parameter

Kadang tipe generic hanya masuk akal untuk sebagian tipe. extends membatasi parameternya sehingga kamu bisa memanggil method dari batas tersebut. Di sini T extends Number berarti T adalah Number atau subclass-nya (Integer, Double, ...), sehingga doubleValue() tersedia:

Perhatikan bahwa extends di sini berarti "merupakan subtipe dari", dan berlaku untuk class maupun interface. <T extends Comparable<T>> sangat sering dipakai saat kamu perlu membandingkan elemen.

Wildcard: ? extends dan ? super

Ada jebakan yang halus: List<Integer> bukan List<Number>, meskipun Integer adalah Number. Generics bersifat invariant. Wildcard melonggarkan aturan ini saat kamu hanya perlu membaca atau hanya perlu menulis.

Pakai ? extends T untuk producer yang kamu baca datanya, dan ? super T untuk consumer yang kamu tulisi data (aturan "PECS", Producer Extends, Consumer Super):

List ? extends Number memungkinkanmu membaca elemen sebagai Number, tetapi tidak bisa ditambahi (compiler tidak bisa tahu tipe elemen yang persis). List ? super Integer memungkinkanmu menambahkan Integer, tetapi hasil bacaannya kembali sebagai Object. Pilih wildcard yang sesuai dengan arah aliran datanya.

Type Erasure dan Batasannya

Generics adalah fitur saat kompilasi. Setelah dikompilasi, type parameter-nya dihapus: saat runtime, Box<String> dan Box<Integer> sama-sama hanya Box. Ini membuat generics tetap kompatibel dengan kode lama, tetapi menimbulkan batasan yang nyata.

// None of these compile - the type parameter doesn't exist at runtime:
T value = new T();          // can't instantiate a type parameter
T[] array = new T[10];      // can't create a generic array
if (list instanceof List<String>) { } // can't test the type argument

Karena tipenya hilang saat runtime, kamu tidak bisa bertanya "tadi T itu apa?" lewat reflection, dan kamu tidak bisa melakukan overload pada method yang hanya berbeda argumen generic-nya (foo(List<String>) dan foo(List<Integer>) dihapus menjadi signature yang sama). Kalau kamu benar-benar butuh tipenya saat runtime, kirim token Class<T> sebagai parameter constructor atau method.

Berikutnya: Lambda Expression

Kamu sudah melihat bahwa generic method menerima tipe sebagai parameter. Langkah berikutnya adalah menjadikan perilaku sebagai parameter. Lambda expression memungkinkanmu mengirim sepotong kode, yaitu sebuah fungsi, ke dalam method. Persis dengan cara itulah kamu akan mengurutkan, menyaring, dan mengubah collection generic yang baru saja kamu pelajari cara memberinya tipe dengan aman.

Pertanyaan yang sering diajukan

Apa itu generics di Java?

Generics memungkinkanmu menulis class atau method yang bekerja dengan tipe yang baru kamu tentukan nanti, bukan terkunci pada satu tipe konkret. Kamu mendeklarasikan type parameter di dalam kurung sudut, class Box<T>, lalu pemanggilnya mengisi tipe yang sebenarnya, Box<String>. Compiler kemudian menegakkan tipe itu di semua tempat, sehingga ketidakcocokan tertangkap saat kompilasi dan kamu tidak perlu melakukan cast secara manual.

Kenapa memakai generics, bukan Object?

Memakai Object menghilangkan seluruh informasi tipe: compiler tidak bisa mencegahmu memasukkan sesuatu yang salah, dan kamu harus melakukan cast pada setiap nilai yang keluar (dengan risiko ClassCastException saat runtime). Generics memindahkan pemeriksaan itu ke waktu kompilasi. List<String> tidak akan menerima Integer, dan get() sudah langsung mengembalikan String: tanpa cast, tanpa kejutan saat runtime.

Apa itu type erasure pada generics Java?

Type erasure berarti informasi tipe generic hanya ada saat kompilasi. Setelah dikompilasi, List<String> dan List<Integer> sama-sama hanya List saat runtime, karena type parameter-nya dihapus. Itulah sebabnya kamu tidak bisa menulis new T[10], memanggil list instanceof List<String>, atau membaca type parameter lewat reflection. Generics memberimu keamanan saat kompilasi, bukan data tipe saat runtime.

Ilustrasi bahasa pemrograman di Coddy

Belajar coding dengan Coddy

Mulai