Mengolah data bertipe campuran di Java klasik selalu berpasangan dua langkah: uji tipe dengan instanceof, lalu cast eksplisit ke variabel baru agar komponennya terbuka. Pola if (x instanceof String) { String s = (String) x; ... } berulang di mana-mana, dan compiler sebenarnya sudah tahu bahwa cast itu pasti berhasil — informasinya hanya tidak dibagikan. Pattern matching menggabungkan pengujian dan pengikatan menjadi satu konstruksi: if (x instanceof String s). Pattern matching untuk instanceof difinalkan di Java 16 (JEP 394), lalu diperluas ke switch di Java 21 melalui JEP 441; keduanya standar di Java 25 dan menjadi fondasi record pattern pada chapter berikutnya.

Type Pattern pada instanceof #

Konstruksi tipe nama disebut type pattern: saat runtime, JVM menguji apakah nilai bertipe yang disebut; bila cocok, variabel diikat otomatis dengan tipe yang sudah tepat — tanpa cast. Cakupan variabelnya mengikuti alur (flow scoping): variabel hanya pasti terikat di cabang yang lolos pengujian, sehingga bisa langsung dipakai di dalam blok if maupun pada kondisi && berikutnya.

java
if (nilai instanceof String teks && teks.length() > 3) {
    System.out.println(teks.toUpperCase()); // teks sudah bertipe String
}

Type Pattern pada switch dan Kondisi when #

Pada switch, type pattern menggantikan pemeriksaan berantai if-else instanceof. Klausa when menambahkan syarat pada label yang sama — begitu satu label berpola sama perlu dibedakan lebih lanjut. Contoh teruji berikut menghitung luas bangun datar dari hierarki tertutup:

import java.util.List;

public class PencocokanTipe {

    sealed interface BangunDatar permits Lingkaran, Persegi, PersegiPanjang { }
    record Lingkaran(double jari) implements BangunDatar { }
    record Persegi(double sisi) implements BangunDatar { }
    record PersegiPanjang(double lebar, double tinggi) implements BangunDatar { }

    // Gaya lama: uji tipe lalu cast manual ke variabel baru.
    static String labelLama(Object nilai) {
        if (nilai instanceof String) {
            String teks = (String) nilai;
            return "teks: " + teks.length() + " karakter";
        }
        return "bukan teks";
    }

    // Pattern matching: uji tipe sekaligus mengikat variabel teks.
    static String labelBaru(Object nilai) {
        if (nilai instanceof String teks) {
            return "teks: " + teks.length() + " karakter";
        }
        return "bukan teks";
    }

    static double luas(BangunDatar b) {
        return switch (b) {
            case Lingkaran l when l.jari() > 0 -> Math.PI * l.jari() * l.jari();
            case Lingkaran l -> 0;                      // jari() <= 0
            case Persegi p -> p.sisi() * p.sisi();
            case PersegiPanjang pp -> pp.lebar() * pp.tinggi();
        };
    }

    public static void main(String[] args) {
        System.out.println("lama : " + labelLama("Halo"));
        System.out.println("baru : " + labelBaru(42));

        var bentuk = List.of(
                new Lingkaran(7),
                new Persegi(9),
                new PersegiPanjang(4, 5),
                new Lingkaran(-1));
        for (BangunDatar b : bentuk) {
            System.out.printf("%-32s luas %.2f%n", b, luas(b));
        }
    }
}

Output yang Dihasilkan:

text
lama : teks: 4 karakter
baru : bukan teks
Lingkaran[jari=7.0]              luas 153.94
Persegi[sisi=9.0]                luas 81.00
PersegiPanjang[lebar=4.0, tinggi=5.0] luas 20.00
Lingkaran[jari=-1.0]             luas 0.00

Bedah baris-per-baris untuk bagian pentingnya:

  1. Method labelLama dan labelBaru: keduanya menghasilkan keluaran sama; versi pattern menghapus cast manual dan variabel sementara karena teks langsung terikat dengan tipe String.
  2. Baris case Lingkaran l when l.jari() > 0 -> ...: label berpola Lingkaran dengan syarat tambahan — lingkaran berjari-jari positif dihitung normal.
  3. Baris case Lingkaran l -> 0;: label kedua berpola sama menampung sisa kasus (jari() <= 0), terlihat pada baris output Lingkaran[jari=-1.0] yang luasnya nol.
  4. Baris case PersegiPanjang pp -> pp.lebar() * pp.tinggi();: variabel pola pp memanggil accessor tanpa cast; berkat hierarki sealed, tidak perlu default.

Urutan Label dan Dominasi #

Label switch berpola dievaluasi dari atas ke bawah, sehingga label yang lebih umum harus diletakkan setelah yang lebih spesifik. Compiler aktif menolak label yang tak mungkin terjangkau — kita verifikasi langsung error berikut pada JDK 25:

text
error: this case label is dominated by a preceding case label

Error itu muncul ketika case Object x ditulis sebelum case String s: pola Object menyerap seluruh nilai, sehingga pola String di bawahnya tidak akan pernah terpilih. Aturan dominance ini melindungi kita dari label mati, sama seperti unreachable statement pada kode berurutan.

Di balik layar, compiler menerjemahkan pola menjadi pemeriksaan bertingkat yang setara dengan versi instanceof manual — hasil bytecode tidak mengandung mekanisme baru yang rahasia. Bedanya murni pada jaminan: compiler memeriksa urutan, dominasi, dan kelengkapan label sebelum program berjalan, pekerjaan yang pada gaya lama dilakukan mata manusia dengan tingkat kelelahan yang berbeda-beda.

KonstruksiSejakFungsi
x instanceof T tJava 16Uji tipe + pengikatan variabel
case T t ->Java 21Type pattern pada switch
case T t when syarat ->Java 21Type pattern dengan kondisi tambahan
case nullJava 21Menangani null secara eksplisit

Type pattern membidik tipe dan mengikat satu variabel; record pattern melangkah lebih jauh dengan membedah komponen di dalamnya langsung pada label — materi chapter Record Patterns.

Referensi #

Berkas Sumber

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

PencocokanTipe.java

Unduh berkas
java
import java.util.List;

public class PencocokanTipe {

    sealed interface BangunDatar permits Lingkaran, Persegi, PersegiPanjang { }
    record Lingkaran(double jari) implements BangunDatar { }
    record Persegi(double sisi) implements BangunDatar { }
    record PersegiPanjang(double lebar, double tinggi) implements BangunDatar { }

    // Gaya lama: uji tipe lalu cast manual ke variabel baru.
    static String labelLama(Object nilai) {
        if (nilai instanceof String) {
            String teks = (String) nilai;
            return "teks: " + teks.length() + " karakter";
        }
        return "bukan teks";
    }

    // Pattern matching: uji tipe sekaligus mengikat variabel teks.
    static String labelBaru(Object nilai) {
        if (nilai instanceof String teks) {
            return "teks: " + teks.length() + " karakter";
        }
        return "bukan teks";
    }

    static double luas(BangunDatar b) {
        return switch (b) {
            case Lingkaran l when l.jari() > 0 -> Math.PI * l.jari() * l.jari();
            case Lingkaran l -> 0;                      // jari() <= 0
            case Persegi p -> p.sisi() * p.sisi();
            case PersegiPanjang pp -> pp.lebar() * pp.tinggi();
        };
    }

    public static void main(String[] args) {
        System.out.println("lama : " + labelLama("Halo"));
        System.out.println("baru : " + labelBaru(42));

        var bentuk = List.of(
                new Lingkaran(7),
                new Persegi(9),
                new PersegiPanjang(4, 5),
                new Lingkaran(-1));
        for (BangunDatar b : bentuk) {
            System.out.printf("%-32s luas %.2f%n", b, luas(b));
        }
    }
}