Sebagian besar class di aplikasi sehari-hari sebenarnya hanya membawa data: Pelanggan berisi nama, email, dan poin — bukan entitas dengan siklus hidup rumit. Gaya lama memaksa kita menulis constructor, getter untuk setiap field, equals, hashCode, dan toString secara manual, sehingga satu class data sederhana membengkak menjadi lima puluh baris yang hampir seluruhnya berulang. Record, difinalkan di Java 16 melalui JEP 395, memampatkan class data semacam itu menjadi satu baris deklarasi header berisi nama dan tipe tiap komponen. Chapter ini membahas apa yang dibuat compiler di balik layar, validasi lewat compact constructor, serta batas pemakaian record yang tepat.

Apa yang Dibuat Compiler #

Sintaks record Pelanggan(String nama, String email, int poin) { } memicu compiler javac membuat class final dengan tiga field private final, constructor kanonik yang mengisi ketiganya, accessor nama(), email(), poin() (tanpa awalan get), serta implementasi equals, hashCode, dan toString berbasis seluruh komponen. Semua method itu mengikuti disjointness: dua record dianggap sama bila bertipe sama dan seluruh komponennya sama nilainya — semantik nilai, bukan semantik identitas objek. Karena field-nya final tanpa setter, record bersifat immutable; perubahan data dikerjakan lewat salinan baru.

Data Pelanggan dalam Satu Berkas #

Contoh teruji berikut merangkum fasilitas record yang dipakai sehari-hari: validasi, factory bernama, pola salinan, hingga integrasi dengan Set:

import java.util.HashSet;
import java.util.Set;

public class DataTransparan {

    // Record: pembawa data transparan + validasi compact constructor.
    record Pelanggan(String nama, String email, int poin) {

        Pelanggan {
            if (!email.endsWith("@example.com")) {
                throw new IllegalArgumentException(
                        "email harus @example.com: " + email);
            }
            if (poin < 0) {
                throw new IllegalArgumentException("poin tidak boleh negatif");
            }
        }

        // Factory bernama untuk kasus umum: pelanggan baru tanpa poin.
        static Pelanggan baru(String nama, String email) {
            return new Pelanggan(nama, email, 0);
        }

        // Pola "wither": salinan baru dengan satu field diubah.
        Pelanggan tambahPoin(int tambahan) {
            return new Pelanggan(nama, email, poin + tambahan);
        }
    }

    interface Berlabel { String label(); }

    record Tag(String kode) implements Berlabel {
        @Override
        public String label() { return "#" + kode; }
    }

    public static void main(String[] args) {
        var budi = new Pelanggan("Budi", "[email protected]", 120);
        var budiDuplikat = new Pelanggan("Budi", "[email protected]", 120);
        var sita = Pelanggan.baru("Sita", "[email protected]");

        System.out.println("toString   : " + budi);
        System.out.println("equals     : " + budi.equals(budiDuplikat));
        System.out.println("hashCode   : " + (budi.hashCode() == budiDuplikat.hashCode()));
        System.out.println("poin Sita  : " + sita.poin());

        var naik = budi.tambahPoin(30);
        System.out.println("poin Budi  : " + budi.poin() + " -> " + naik.poin());

        Set<Pelanggan> himpunan = new HashSet<>();
        himpunan.add(budi);
        himpunan.add(budiDuplikat);
        System.out.println("set berisi : " + himpunan.size() + " elemen");

        System.out.println("label tag  : " + new Tag("java25").label());

        try {
            new Pelanggan("Dewi", "[email protected]", 0);
        } catch (IllegalArgumentException e) {
            System.out.println("validasi   : " + e.getMessage());
        }
    }
}

Output yang Dihasilkan:

text
toString   : Pelanggan[nama=Budi, [email protected], poin=120]
equals     : true
hashCode   : true
poin Sita  : 0
poin Budi  : 120 -> 150
set berisi : 1 elemen
label tag  : #java25
validasi   : email harus @example.com: [email protected]

Bedah baris-per-baris untuk bagian pentingnya:

  1. Blok Pelanggan { ... } tanpa parameter: compact constructor — tempat validasi sebelum field diisi. Compiler tetap mengisi field otomatis setelah blok ini selesai; kita cukup melempar IllegalArgumentException bila data tidak sah.
  2. Method static Pelanggan baru(String nama, String email): factory bernama untuk kasus umum (pelanggan baru, poin nol); constructor kanonik tetap tersedia untuk kasus lain.
  3. Method tambahPoin(int tambahan): pola wither — mengembalikan record baru dengan satu komponen diganti, karena budi yang lama tidak mungkin diubah.
  4. Baris System.out.println("toString : " + budi): format bawaan Pelanggan[nama=Budi, [email protected], poin=120] menyertakan seluruh komponen — langsung berguna untuk logging.
  5. Blok Set<Pelanggan> himpunan: karena equals dan hashCode dihitung dari nilai, budi dan budiDuplikat menyatu menjadi satu elemen himpunan.

Batas dan Pemakaian yang Tepat #

Record bersifat final: tidak bisa diwarisi, dan tidak bisa mewarisi class lain — hanya mengimplementasikan interface seperti Berlabel pada contoh. Field static dan method tambahan bebas seperti biasa, termasuk record bersarang (otomatis static) dan record lokal di dalam method.

AspekClass biasaRecord
Boilerplate dataDitulis manualDihasilkan compiler
MutabilitasBebasKomponen selalu final
PewarisanBisa diwarisiFinal, hanya interface
Semantik kesamaanSebagai kita tulisBerdasar nilai komponen
Cocok untukEntitas berperilaku & berubahDTO, kunci peta, hasil kembalian

Sebagai pelengkap praktis, record juga boleh dideklarasikan lokal di dalam method — berguna untuk tipe bantu sementara seperti pasangan hasil antara. Record juga mendukung instanceof berpola dan deconstruction lewat record pattern yang kita bahas tuntas di chapter Record Patterns. Untuk kunci komposit pada Map (misalnya pasangan kota dan bulan), record jauh lebih aman daripada List<Object> karena tipe tiap komponen diperiksa compiler.

Satu batas masih terasa: siapa yang boleh mengimplementasikan interface Berlabel tetap terbuka bagi class mana pun, sehingga switch atas tipe implementasi tidak bisa dijamin lengkap oleh compiler. Sealed classes menutup celah itu — pokok bahasan chapter berikutnya.

Referensi #

Berkas Sumber

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

DataTransparan.java

Unduh berkas
java
import java.util.HashSet;
import java.util.Set;

public class DataTransparan {

    // Record: pembawa data transparan + validasi compact constructor.
    record Pelanggan(String nama, String email, int poin) {

        Pelanggan {
            if (!email.endsWith("@example.com")) {
                throw new IllegalArgumentException(
                        "email harus @example.com: " + email);
            }
            if (poin < 0) {
                throw new IllegalArgumentException("poin tidak boleh negatif");
            }
        }

        // Factory bernama untuk kasus umum: pelanggan baru tanpa poin.
        static Pelanggan baru(String nama, String email) {
            return new Pelanggan(nama, email, 0);
        }

        // Pola "wither": salinan baru dengan satu field diubah.
        Pelanggan tambahPoin(int tambahan) {
            return new Pelanggan(nama, email, poin + tambahan);
        }
    }

    interface Berlabel { String label(); }

    record Tag(String kode) implements Berlabel {
        @Override
        public String label() { return "#" + kode; }
    }

    public static void main(String[] args) {
        var budi = new Pelanggan("Budi", "[email protected]", 120);
        var budiDuplikat = new Pelanggan("Budi", "[email protected]", 120);
        var sita = Pelanggan.baru("Sita", "[email protected]");

        System.out.println("toString   : " + budi);
        System.out.println("equals     : " + budi.equals(budiDuplikat));
        System.out.println("hashCode   : " + (budi.hashCode() == budiDuplikat.hashCode()));
        System.out.println("poin Sita  : " + sita.poin());

        var naik = budi.tambahPoin(30);
        System.out.println("poin Budi  : " + budi.poin() + " -> " + naik.poin());

        Set<Pelanggan> himpunan = new HashSet<>();
        himpunan.add(budi);
        himpunan.add(budiDuplikat);
        System.out.println("set berisi : " + himpunan.size() + " elemen");

        System.out.println("label tag  : " + new Tag("java25").label());

        try {
            new Pelanggan("Dewi", "[email protected]", 0);
        } catch (IllegalArgumentException e) {
            System.out.println("validasi   : " + e.getMessage());
        }
    }
}