Satu type parameter sudah cukup untuk wadah seperti Kotak<T>, tetapi data nyata sering berbentuk gabungan: nama dan umur, produk dan harga, kunci dan nilai. Java mengizinkan sebuah class atau method mendeklarasikan beberapa type parameter sekaligus, dipisahkan koma di dalam satu tanda kurung sudut — pola yang paling terkenal dari Map<K, V> di pustaka standar. Setiap type parameter berdiri independen: K boleh diisi String sementara V diisi Integer, dan compiler javac memeriksa keduanya secara terpisah. Pada Chapter ini kita bangun struktur Pasangan<K, V> dan Triple<A, B, C> sendiri, lalu bertemu dengan pasangan resmi dari pustaka standar: Map.Entry<K, V>.

Class Pasangan dengan Dua Type Parameter #

Deklarasi multi type parameter tidak berbeda dari satu type parameter — hanya daftar yang lebih panjang. Berikut class Pasangan yang menyimpan dua nilai dengan dua tipe berbeda:

java Pasangan.java
public class Pasangan<K, V> {
    private final K kiri;
    private final V kanan;

    public Pasangan(K kiri, V kanan) {
        this.kiri = kiri;
        this.kanan = kanan;
    }

    public K getKiri() {
        return kiri;
    }

    public V getKanan() {
        return kanan;
    }

    @Override
    public String toString() {
        return "(" + kiri + ", " + kanan + ")";
    }
}

Class di atas memakai K untuk nilai kiri dan V untuk nilai kanan. Keduanya bersifat final, diisi lewat constructor, dan dibaca lewat getter yang masing-masing mengembalikan tipe aslinya — getKiri() mengembalikan K, getKanan() mengembalikan V, tanpa cast di sisi pemanggil.

Pemakaian Pasangan, Triple, dan Map Entry #

Satu definisi Pasangan<K, V> melayani kombinasi tipe berbeda-beda, ditambah class Triple dengan tiga type parameter dan pemakaian Map.Entry dari pustaka standar:

import java.util.LinkedHashMap;
import java.util.Map;

public class AplikasiPasangan {
    public static void main(String[] args) {
        Pasangan<String, Integer> umurBudi = new Pasangan<>("Budi", 17);
        Pasangan<String, Double> beratSiti = new Pasangan<>("Siti", 48.5);
        System.out.println("Umur  : " + umurBudi.getKiri() + " = " + umurBudi.getKanan());
        System.out.println("Berat : " + beratSiti.getKiri() + " = " + beratSiti.getKanan());

        int tahunDepan = umurBudi.getKanan() + 3;   // Integer, tanpa cast
        System.out.println("Umur 3 tahun lagi: " + tahunDepan);

        // Tiga type parameter sekaligus
        Triple<String, Integer, Boolean> baris = new Triple<>("pensil", 12, true);
        System.out.println("Triple: " + baris);

        // Map.Entry: pasangan bawaan pustaka standar
        Map<String, Integer> stok = new LinkedHashMap<>();
        stok.put("pensil", 12);
        stok.put("buku", 7);
        for (Map.Entry<String, Integer> entri : stok.entrySet()) {
            System.out.println(entri.getKey() + " -> " + entri.getValue());
        }
    }
}

class Triple<A, B, C> {
    private final A pertama;
    private final B kedua;
    private final C ketiga;

    Triple(A pertama, B kedua, C ketiga) {
        this.pertama = pertama;
        this.kedua = kedua;
        this.ketiga = ketiga;
    }

    @Override
    public String toString() {
        return "(" + pertama + ", " + kedua + ", " + ketiga + ")";
    }
}

Output yang Dihasilkan:

text
Umur  : Budi = 17
Berat : Siti = 48.5
Umur 3 tahun lagi: 20
Triple: (pensil, 12, true)
pensil -> 12
buku -> 7

Mari kita bedah baris-baris pentingnya:

  1. Baris 6-7 (new Pasangan<>("Budi", 17) dan new Pasangan<>("Siti", 48.5)): dua objek dari class yang sama dengan kombinasi type argument berbeda — Pasangan<String, Integer> dan Pasangan<String, Double>. Diamond <> menyimpulkan keduanya dari argument.
  2. Baris 11 (umurBudi.getKanan() + 3): hasil getKanan() bertipe Integer persis, sehingga operasi aritmetika langsung sah tanpa cast dari Object.
  3. Baris 15-16 (Triple<String, Integer, Boolean>): jumlah type parameter tidak dibatasi dua — tiga, empat, atau lebih sah secara bahasa. Konvensi hanya menganjurkan memakai huruf berurutan (A, B, C) atau bermakna (K, V).
  4. Baris 22-24 (Map.Entry<String, Integer>): entrySet() menghasilkan Set<Map.Entry<String, Integer>> — pasangan kunci-nilai resmi dari Map<K, V>. Method getKey() dan getValue() bekerja seperti getKiri() dan getKanan() milik Pasangan.

Urutan Type Parameter Adalah Kontrak #

Nama type parameter hanya pengenal; yang memberi makna adalah urutannya. Pasangan<String, Integer> berarti “kiri String, kanan Integer” — dan menukar urutan argument akan ditolak compiler. Verifikasi di JDK 25 memperlihatkan konstruksi new Pasangan<>(17, "Budi") untuk variabel bertipe Pasangan<String, Integer> gagal dengan:

text
CekTukar.java:3: error: incompatible types: cannot infer type arguments for PasanganX<>
        PasanganX<String, Integer> p = new PasanganX<>(17, "Budi");
                                                    ^
  reason: inference variable K has incompatible bounds
      equality constraints: String
      lower bounds: Integer

Pesan tersebut memperlihatkan inferensi bekerja per posisi: argument 17 menuntut K = Integer, sedangkan deklarasi variabel menuntut K = String — konflik yang tidak mungkin didamaikan. Pasangan<String, Integer> dan Pasangan<Integer, String> adalah dua tipe berbeda, bukan tulisan ganda dari tipe yang sama.

Dua type parameter menyelesaikan satu keluarga masalah, tetapi membuka keluarga masalah baru: apakah Pasangan<Integer, String> bisa dianggap subtype dari Pasangan<Number, String>? Jawabannya tidak — generics Java bersifat invariant, dan inilah alasan wildcard ? diciptakan. Chapter Wildcards membedah topik tersebut selangkah demi selangkah.

Referensi #

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

AplikasiPasangan.java

Unduh berkas
java
import java.util.LinkedHashMap;
import java.util.Map;

public class AplikasiPasangan {
    public static void main(String[] args) {
        Pasangan<String, Integer> umurBudi = new Pasangan<>("Budi", 17);
        Pasangan<String, Double> beratSiti = new Pasangan<>("Siti", 48.5);
        System.out.println("Umur  : " + umurBudi.getKiri() + " = " + umurBudi.getKanan());
        System.out.println("Berat : " + beratSiti.getKiri() + " = " + beratSiti.getKanan());

        int tahunDepan = umurBudi.getKanan() + 3;   // Integer, tanpa cast
        System.out.println("Umur 3 tahun lagi: " + tahunDepan);

        // Tiga type parameter sekaligus
        Triple<String, Integer, Boolean> baris = new Triple<>("pensil", 12, true);
        System.out.println("Triple: " + baris);

        // Map.Entry: pasangan bawaan pustaka standar
        Map<String, Integer> stok = new LinkedHashMap<>();
        stok.put("pensil", 12);
        stok.put("buku", 7);
        for (Map.Entry<String, Integer> entri : stok.entrySet()) {
            System.out.println(entri.getKey() + " -> " + entri.getValue());
        }
    }
}

class Triple<A, B, C> {
    private final A pertama;
    private final B kedua;
    private final C ketiga;

    Triple(A pertama, B kedua, C ketiga) {
        this.pertama = pertama;
        this.kedua = kedua;
        this.ketiga = ketiga;
    }

    @Override
    public String toString() {
        return "(" + pertama + ", " + kedua + ", " + ketiga + ")";
    }
}

Pasangan.java

Unduh berkas
java
public class Pasangan<K, V> {
    private final K kiri;
    private final V kanan;

    public Pasangan(K kiri, V kanan) {
        this.kiri = kiri;
        this.kanan = kanan;
    }

    public K getKiri() {
        return kiri;
    }

    public V getKanan() {
        return kanan;
    }

    @Override
    public String toString() {
        return "(" + kiri + ", " + kanan + ")";
    }
}