Metode generic menurunkan ide parameter tipe ke tingkat metode, sehingga satu metode dapat melayani array atau objek dari tipe apa pun yang cocok. Topik ini membahas tempat penulisan parameter tipe pada deklarasi metode, cara type inference menyimpulkan tipenya saat pemanggilan, serta hubungan metode generic dengan anggota static. Setelah menyelesaikannya, metode utilitas yang aman untuk tipe apa pun dapat ditulis tanpa menduplikasi kodenya. Pola yang sama dipakai pustaka standar, misalnya pada pabrik List.of dan utilitas Collections.
Mendeklarasikan metode generic#
Parameter tipe metode ditulis di antara tanda < dan > tepat sebelum tipe kembalian, misalnya static <T> void cetakArray(T[] data). Urutan itu wajib, sehingga bentuk static void cetakArray<T>(T[] data) tidak akan terkompilasi. Sejak dideklarasikan, T berlaku di seluruh tanda tangan metode dan tubuhnya, lalu hilang setelah metode berakhir. Dua metode dalam program berikut melayani array bertipe apa pun dengan satu definisi.
public class UjiMetodeGeneric {
public static void main(String[] args) {
String[] nama = {"Ali", "Budi", "Citra"};
Integer[] nilai = {78, 92, 85};
cetakArray(nama);
cetakArray(nilai);
tukar(nama, 0, 2);
System.out.print("Setelah tukar : ");
cetakArray(nama);
}
static <T> void cetakArray(T[] data) {
for (int i = 0; i < data.length; i++) {
if (i > 0) {
System.out.print(", ");
}
System.out.print(data[i]);
}
System.out.println();
}
static <T> void tukar(T[] data, int i, int j) {
T sementara = data[i];
data[i] = data[j];
data[j] = sementara;
}
}Ali, Budi, Citra
78, 92, 85
Setelah tukar : Citra, Budi, AliMetode cetakArray dipanggil dua kali dengan argumen bertipe berbeda, dan setiap pemanggilan mengikat T ke String lalu ke Integer. Metode tukar memakai T sebagai tipe variabel lokal sementara, sehingga pertukaran dua elemen tidak pernah kehilangan tipenya. Kedua metode ini static, dan itu bukan kebetulan. Metode utilitas semacam ini tidak menyimpan data milik objek, sehingga bentuk static adalah pilihan yang alami.
Perlu dicatat bahwa kedua metode itu berada di kelas biasa yang tidak membawa parameter tipe apa pun. Metode generic memang tidak menuntut kelasnya generic, dan justru bentuk inilah yang paling sering muncul pada kelas utilitas. Letak <T> setelah static juga berlaku sama untuk metode instance, misalnya <T> T pilih(T a, T b), sehingga aturan penempatannya cukup diingat satu kali.
Type inference saat pemanggilan#
Pemanggilan metode generic biasanya ditulis tanpa argumen tipe karena kompilator menjalankan type inference, yakni proses menyimpulkan argumen tipe dari tipe nilai yang diterima metode tersebut. Pemanggilan pilihLebihPanjang("Kulkas", "Rak") menyimpulkan T sebagai String, sedangkan pilihLebihPanjang(7, 42) menyimpulkan T sebagai Integer. Pada kasus kedua, literal 7 dan 42 dikotakkan (autoboxing) menjadi Integer lebih dulu, karena tipe primitif tidak boleh menjadi argumen tipe.
public class UjiInferensi {
public static void main(String[] args) {
String kata = pilihLebihPanjang("Kulkas", "Rak");
System.out.println("Kata terpilih : " + kata);
Integer angka = pilihLebihPanjang(7, 42);
System.out.println("Angka terpilih : " + angka);
var hasil = UjiInferensi.<String>pilihLebihPanjang("Baja", "Kayu");
System.out.println("Saksi tipe : " + hasil);
}
static <T> T pilihLebihPanjang(T a, T b) {
String teksA = String.valueOf(a);
String teksB = String.valueOf(b);
return teksA.length() >= teksB.length() ? a : b;
}
}Kata terpilih : Kulkas
Angka terpilih : 42
Saksi tipe : BajaKeluaran program menegaskan tiga bentuk pemanggilan itu. Kata terpilih Kulkas berasal dari inferensi String yang murni membaca argumennya, angka 42 berasal dari inferensi Integer yang melibatkan autoboxing, sedangkan baris terakhir memakai saksi tipe <String> secara eksplisit. Awalan UjiInferensi. pada saksi tipe diperlukan karena metodenya static, sedangkan pada pemanggilan lewat objek saksi tipenya cukup ditulis setelah titik milik variabelnya.
Inferensi juga membaca konteks penugasan, sehingga penugasan ke variabel bertipe Integer ikut menjadi petunjuk bagi kompilator. Bila hasil inferensi tidak sesuai harapan, saksi tipe (type witness) eksplisit dapat ditulis, yaitu UjiInferensi.<String>pilihLebihPanjang("Baja", "Kayu"). Bentuk seperti ini jarang diperlukan, tetapi berguna ketika dua argumen bertipe berjauhan sehingga kompilator menyimpulkan tipe gabungan yang paling umum, misalnya Object, alih-alih tipe yang dimaksud.
Metode generic dan static#
Metode static tidak boleh memakai parameter tipe milik kelasnya. Parameter tipe kelas terikat pada objek, yaitu setiap objek membawa pilihan tipenya sendiri, sehingga Daftar<String> dan Daftar<Integer> melekat pada dua objek yang berbeda. Anggota static tidak melekat pada objek mana pun, maka tidak ada pilihan tipe yang bisa dibacanya. Deklarasi berikut karena itu ditolak kompilator.
// tidak-dijalankan
class Daftar<T> {
T nilai;
static T salinTunggal(T sumber) {
return sumber;
}
}
// error: non-static type variable T cannot be referenced from a static contextJalan keluarnya sederhana. Metode static yang butuh tipe bebas mendeklarasikan parameter tipe miliknya sendiri, misalnya static <U> U salinTunggal(U sumber) { return sumber; }. Kedua metode pada UjiMetodeGeneric di awal topik memakai pola ini, yaitu static sekaligus membawa <T> milik metodenya. Dengan begitu metode static tetap bisa generic tanpa bergantung pada parameter tipe kelas.
Kesalahan umum#
- Menaruh daftar parameter tipe setelah tipe kembalian, misalnya
static void cetakArray<T>(T[] data). Sintaks yang sah adalahstatic <T> void cetakArray(T[] data), yaitu daftar parameter tipe berada sebelum tipe kembalian. - Mengira
pilihLebihPanjang(7, 42)menyimpulkan T sebagaiint. Tipe primitif tidak boleh menjadi argumen tipe, sehingga kedua literal dikotakkan menjadi Integer dan nilai kembaliannya pun Integer. Sadari boxing ini bila kecepatan atau perbandingan tipe menjadi penting. - Memakai parameter tipe kelas di dalam metode static. Kompilator menolaknya dengan pesan
non-static type variable, dan perbaikannya adalah mendeklarasikan parameter tipe baru pada metode tersebut. - Menggabungkan argumen bertipe berjauhan dalam satu pemanggilan, misalnya
pilihLebihPanjang("Kulkas", 42). Inferensi menyimpulkan tipe gabungan terluas dari keduanya, sehingga nilai kembaliannya tidak bisa ditugaskan ke String maupun Integer dan penugasannya gagal dengan pesanincompatible types. Samakan tipe kedua argumen bila hasilnya memang ingin dipakai.
Ringkasan#
- Metode generic mendeklarasikan parameter tipenya sebelum tipe kembalian, misalnya
static <T> void cetakArray(T[] data). - Type inference menyimpulkan argumen tipe dari argumen pemanggilan dan konteks penugasan, sedangkan saksi tipe eksplisit dipakai bila hasil inferensinya menyimpang.
- Tipe primitif tidak bisa menjadi argumen tipe, sehingga literal
intdikotakkan menjadi Integer sebelum inferensi berjalan. - Metode static tidak boleh memakai parameter tipe kelas dan wajib mendeklarasikan parameter tipe miliknya bila butuh tipe bebas.
Referensi#
- Generic Methods — Oracle — tutorial resmi yang memperkenalkan deklarasi dan pemanggilan metode generic.
- Type Inference — Oracle — aturan penyimpulan argumen tipe dan penulisan saksi tipe eksplisit.