Record

Membuat pembawa data imutabel dengan record Java 16 dan menilai kapan record lebih tepat daripada kelas.

Daftar isi

Sebagian besar kelas tidak lebih dari pembawa data, yaitu tempat menyimpan beberapa nilai yang dikirim bersama-sama dari satu titik kode ke titik lain. Java 16 memperkenalkan record untuk kasus ini, sebuah bentuk kelas ringkas yang satu baris deklarasinya menghasilkan konstruktor, aksesor, dan perbandingan yang benar. Topik ini mendeklarasikan record pertama, menambahkan validasi lewat konstruktor compact, lalu menimbang kapan record lebih tepat daripada kelas biasa.

Mendeklarasikan record#

Record dideklarasikan dengan kata kunci record diikuti nama dan daftar komponen (component) di dalam tanda kurung. Daftar itu adalah seluruh keadaan record, dan dari situlah kompilator merangkai sisanya secara otomatis, tanpa field yang ditulis manual dan tanpa metode yang disalin-tempel. Bentuk paling ringkasnya bahkan hanya satu baris, seperti record Titik(int x, int y) {}.

java UjiRecordTitik.java
public class UjiRecordTitik {

    public static void main(String[] args) {
        Titik titikA = new Titik(3, 4);
        Titik titikB = new Titik(3, 4);
        System.out.println("titikA : " + titikA);
        System.out.println("x      : " + titikA.x());
        System.out.println("y      : " + titikA.y());
        System.out.println("equals : " + titikA.equals(titikB));
    }
}

record Titik(int x, int y) {
}
text Keluaran
titikA : Titik[x=3, y=4]
x      : 3
y      : 4
equals : true

Dari dua komponen itu, kompilator menghasilkan empat hal sekaligus. Konstruktor kanonik menerima x dan y sesuai urutan komponennya, aksesor bernama x() dan y() membaca nilainya, field internal dibuat private final sehingga tidak ada pengubah, serta toString, equals, dan hashCode dihitung dari komponen. Perhatikan dua kebiasaan baru pada keluarannya. Aksesor bernama sama dengan komponennya, yaitu x() dan bukan getX(), dan equals langsung bernilai true untuk dua record berisi sama, tanpa satu baris perbandingan yang ditulis manual.

Konstruktor compact untuk validasi#

Nilai komponen biasanya perlu diperiksa sebelum disimpan, misalnya suhu tidak boleh berada di bawah nol mutlak. Record menyediakan konstruktor compact untuk keperluan itu, yaitu konstruktor dengan nama record tanpa daftar parameter, karena parameternya sudah dipahami dari komponen. Tubuhnya berjalan di tengah proses konstruksi kanonik, tepat sebelum nilai komponen diisikan ke field.

java UjiRecordSuhu.java
public class UjiRecordSuhu {

    public static void main(String[] args) {
        Suhu ruangan = new Suhu(25.5);
        Suhu freezer = new Suhu(-18.0);
        System.out.println("Ruangan : " + ruangan.celsius() + " derajat");
        System.out.println("Freezer : " + freezer.celsius() + " derajat");
    }
}

record Suhu(double celsius) {

    Suhu {
        if (celsius < -273.15) {
            throw new IllegalArgumentException("celsius di bawah nol mutlak");
        }
    }
}
text Keluaran
Ruangan : 25.5 derajat
Freezer : -18.0 derajat

Pemeriksaan di dalam konstruktor compact membuat Suhu yang tidak sah menjadi mustahil ada, karena new Suhu(-300) langsung memunculkan IllegalArgumentException dan tidak ada objek yang dihasilkan. Konstruktor compact juga bisa menormalkan nilai, misalnya menuliskan celsius = Math.round(celsius * 10) / 10.0 sebelum pemeriksaan, dan nilai terakhir itulah yang tersimpan. Dengan pola ini, janji imutabel record sekaligus bertindak sebagai jaminan bahwa nilai yang terkunci adalah nilai yang sah.

Record versus kelas biasa#

Pemilihan antara record dan kelas biasa berputar pada satu pertanyaan, yaitu apakah data itu perlu berubah setelah dibuat. Record menjawab kebutuhan pembawa data yang sekali dibentuk lalu dibaca, sedangkan kelas biasa memberi kebebasan penuh atas mutabilitas dan pewarisan.

AspekrecordKelas biasa
Keadaan setelah dibuatterkunci, semua komponen finalbebas dirancang, bisa berubah
equals, hashCode, toStringotomatis dari komponenditulis dan dijaga sendiri
Nama aksesorsama dengan komponen, misalnya x()bebas, lazimnya getX()
Pewarisanfinal, memperluas java.lang.Recordbebas extends dan diperluas
Paling cocok untukpembawa data seperti titik, suhu, hasil perhitunganentitas dengan keadaan yang berubah, seperti Rekening

Record Rekening akan menimbulkan masalah, karena saldo wajib berubah lewat setor dan tarik yang menjaga invarian, sedangkan record tidak menyediakan jalur pengubahan apa pun. Sebaliknya, menjadikan Titik kelas biasa memaksa penulisan konstruktor, aksesor, equals, hashCode, dan toString secara manual, dengan risiko salah satu terlupa justru pada kelas yang isinya paling sering dibandingkan. Aturan ibu jarinya sederhana, yaitu data yang mengalir dipakai record, sedangkan objek yang hidup dan berubah dipakai kelas biasa.

Kesalahan umum#

  • Mencari metode pengisi seperti setX() pada record. Record imutabel dan tidak menyediakannya, sehingga pembaruan dilakukan dengan membuat record baru berisi nilai baru.
  • Memanggil aksesor dengan awalan get. Aksesor bawaan bernama sama dengan komponennya, misalnya x(), sehingga getX() gagal dikompilasi.
  • Menaruh komponen yang bisa diubah, misalnya array atau daftar, lalu menganggap record aman sepenuhnya. Keimutabelannya dangkal, karena isinya masih dapat dimodifikasi lewat referensi tersebut.
  • Mencoba memperluas kelas lain dari record. Deklarasi record sudah menyiratkan final dan pewarisan terhadap java.lang.Record, sehingga extends lain gagal dikompilasi, kendati implements terhadap interface tetap dibolehkan.

Ringkasan#

  • Record merangkai konstruktor kanonik, aksesor, field private final, serta toString, equals, dan hashCode dari daftar komponennya.
  • Aksesor record bernama sama dengan komponennya, misalnya x(), dan equals bawaan membandingkan isi komponen secara langsung.
  • Konstruktor compact memeriksa atau menormalkan komponen sebelum nilai tersimpan, sehingga record yang tidak sah menjadi mustahil dibuat.
  • Record tepat untuk pembawa data yang sekali dibentuk, sedangkan keadaan yang berubah dengan invarian tetap menjadi ranah kelas biasa.

Referensi#