Record

Record sebagai nilai agregat anonim yang ringan.

Daftar isi

Record adalah nilai agregat anonim (record) yang mengelompokkan beberapa nilai ke dalam satu objek tanpa perlu mendeklarasikan kelas. Topik ini membahas literal record posisi dan bernama, cara mengambil nilai bidangnya, destrukturisasi, kesetaraan struktural, serta batasan record dibandingkan dengan kelas. Setelah membacanya, pemilihan antara record dan kelas untuk data kecil dapat dilakukan dengan dasar yang jelas.

Literal record posisi dan bernama#

Record dibuat dengan menulis nilai-nilainya di dalam tanda kurung, dan bentuk tulisan inilah yang disebut literal record. Literal posisi menuliskan nilai berurutan tanpa label, sedangkan literal bernama menuliskan label sebelum setiap nilai. Contoh berikut menampilkan ketiga bentuknya, yaitu posisi, bernama, dan campuran.

dart Contoh: literal record posisi, bernama, dan campuran
void main() {
  final titik = (3, 4);
  print('Posisi: $titik');
  print('Koordinat x: ${titik.$1}, koordinat y: ${titik.$2}');
  print('Tipe: ${titik.runtimeType}');

  final cuaca = (suhu: 30, tekanan: 1013);
  print('Suhu: ${cuaca.suhu} °C, tekanan: ${cuaca.tekanan} hPa');
  print('Tipe: ${cuaca.runtimeType}');

  final catatan = ('sensor-dalam', suhu: 30);
  print('Catatan: $catatan');
  print('Bidang posisi: ${catatan.$1}, bidang bernama: ${catatan.suhu}');
}
text Keluaran
Posisi: (3, 4)
Koordinat x: 3, koordinat y: 4
Tipe: (int, int)
Suhu: 30 °C, tekanan: 1013 hPa
Tipe: ({int suhu, int tekanan})
Catatan: (sensor-dalam, suhu: 30)
Bidang posisi: sensor-dalam, bidang bernama: 30

Keluaran memperlihatkan dua cara mengambil bidang. Bidang posisi diakses dengan $1, $2, dan seterusnya sesuai urutannya, sedangkan bidang bernama diakses dengan namanya, misalnya cuaca.suhu. Properti runtimeType juga menyingkap bentuk setiap record, yaitu (int, int) untuk record posisi dan ({int suhu, int tekanan}) untuk record bernama. Bentuk inilah yang menjadi tipe record, sehingga dua record dengan urutan bidang yang berbeda memiliki tipe yang berbeda pula. Perbedaan lain terletak pada makna, karena record posisi bergantung sepenuhnya pada urutan penulisan, sedangkan record bernama menyimpan label yang tetap terjaga walaupun urutannya diubah.

Tipe record dan destrukturisasi#

Tipe record ditulis mengikuti bentuknya, sehingga (int, int) menyatakan record posisi dua bilangan bulat dan ({int suhu, int tekanan}) menyatakan record bernama dengan dua bidang. Anotasi tipe ini dapat ditulis pada variabel, tetapi penggunaannya yang paling lazim adalah sebagai tipe kembalian fungsi. Fungsi yang perlu mengembalikan dua nilai sekaligus tidak lagi memerlukan kelas pembungkus (wrapper) ataupun cara lain, karena record menampung kedua nilai tersebut secara langsung. Contoh berikut menghitung rentang suhu dan mengembalikan kedua batasnya dalam satu record.

dart record.dart
// Program log sensor suhu kecil yang memakai record.
// Menunjukkan literal record, tipe record, dan destrukturisasi.

(int, int) rentangSuhu(List<int> pengukuran) {
  var terendah = pengukuran.first;
  var tertinggi = pengukuran.first;
  for (final suhu in pengukuran) {
    if (suhu < terendah) terendah = suhu;
    if (suhu > tertinggi) tertinggi = suhu;
  }
  return (terendah, tertinggi);
}

void main() {
  final pengukuran = [26, 28, 31, 27, 30];
  final (terendah, tertinggi) = rentangSuhu(pengukuran);
  print('Pengukuran sensor: $pengukuran');
  print('Terendah: $terendah, tertinggi: $tertinggi');

  final pembacaan = ('sensor-dalam', suhu: 28.4);
  print('Pembacaan: $pembacaan');
  print('Sensor ${pembacaan.$1} mengukur ${pembacaan.suhu} °C');
}
text Keluaran
Pengukuran sensor: [26, 28, 31, 27, 30]
Terendah: 26, tertinggi: 31
Pembacaan: (sensor-dalam, suhu: 28.4)
Sensor sensor-dalam mengukur 28.4 °C

Baris final (terendah, tertinggi) = rentangSuhu(pengukuran); memperlihatkan destrukturisasi, yaitu pembongkaran sebuah nilai agregat menjadi variabel-variabel terpisah. Pattern (terendah, tertinggi) dicocokkan dengan hasil fungsi, dan setiap bidang langsung menjadi variabel yang namanya sama dengan bidangnya. Mekanisme ini menghilangkan baris pengambilan bidang satu per satu, misalnya hasil.$1 dan hasil.$2, sehingga pembacaan kode lebih dekat dengan maksudnya. Record campuran seperti pembacaan tetap dapat digunakan, sedangkan bidang posisinya diakses melalui $1 karena destrukturisasi tidak dilakukan.

Destrukturisasi juga dapat disusun bertingkat untuk data yang bersarang, misalnya (nama, (terendah, tertinggi)) yang membongkar record luar beserta record dalamnya sekaligus menjadi tiga variabel. Bidang yang tidak diperlukan diabaikan dengan pattern _, sehingga variabel hanya dibuat untuk nilai yang memang digunakan. Bentuk pattern yang mengikuti bentuk data membuat struktur nilai terbaca langsung dari tulisannya.

Kesetaraan struktural#

Dua record dianggap sama apabila bentuknya sama dan setiap bidangnya sama. Pembandingan bidang per bidang ini disebut kesetaraan struktural (structural equality), dan operator == record menerapkannya secara otomatis. Tipe elemen tidak ikut menentukan bentuk, sehingga (3, 4) dan (3.0, 4.0) ternyata sama karena nilai 3 dan 4 sama dengan 3.0 dan 4.0. Sebaliknya, bentuk yang berbeda tidak pernah sama, termasuk record bernama dan record posisi dengan isi yang identik.

dart Contoh: kesetaraan struktural record
void main() {
  final a = (suhu: 30, tekanan: 1013);
  final b = (suhu: 30, tekanan: 1013);
  final c = (suhu: 30, tekanan: 1014);
  print('a == b: ${a == b}');
  print('a == c: ${a == c}');

  final d = (30, 1013);
  print('a == d: ${a == d}');

  final titikA = (3, 4);
  final titikB = (3.0, 4.0);
  print('titikA == titikB: ${titikA == titikB}');
}
text Keluaran
a == b: true
a == c: false
a == d: false
titikA == titikB: true

Keluaran menegaskan bahwa a == b bernilai true karena seluruh bidangnya sama, sedangkan a == c bernilai false karena satu bidangnya berbeda. Perbandingan a == d bernilai false walaupun isinya tampak sama, karena record bernama dan record posisi memiliki bentuk yang tidak sama. Kesetaraan struktural juga menghasilkan hashCode yang struktural, sehingga record dapat digunakan sebagai kunci Map dengan perilaku yang wajar. Perilaku ini menjadikan record pilihan yang cocok untuk membandingkan data yang maknanya terletak pada isinya.

Kesetaraan setiap bidang mengikuti == dari tipe bidangnya, sehingga dua bidang bertipe list dibandingkan dengan kesetaraan identitas objek list. Kesetaraan struktural tetap berlaku untuk record bersarang, karena == record dalam juga membandingkan bidang per bidang. Akibatnya, dua record dengan isi list yang berbeda objeknya dianggap berbeda, sedangkan dua record bersarang dengan isi yang sama dianggap sama.

Record dan kelas#

Kelas memiliki perilaku bawaan yang berbeda dari record, yaitu membandingkan identitas objek. Dua objek kelas yang dibuat terpisah dianggap berbeda walaupun seluruh bidangnya sama, kecuali operator == ditimpa secara manual. Perilaku kedua bentuk ini dapat dibandingkan langsung pada program berikut.

dart Contoh: kelas membandingkan identitas, record membandingkan isi
class Titik {
  final int x;
  final int y;
  Titik(this.x, this.y);
}

void main() {
  final a = Titik(3, 4);
  final b = Titik(3, 4);
  print('Kelas, a == b: ${a == b}');

  final c = (3, 4);
  final d = (3, 4);
  print('Record, c == d: ${c == d}');
}
text Keluaran
Kelas, a == b: false
Record, c == d: true

Tabel berikut merangkum perbedaan keduanya beserta penggunaan yang sesuai.

AspekRecordKelas
DeklarasiTanpa deklarasi, langsung literalMemerlukan deklarasi kelas
KesetaraanBidang per bidang secara strukturalIdentitas objek, kecuali operator == ditimpa
BidangPosisi dan bernama, tanpa perilakuAnggota bernama yang membawa perilaku
Cocok untukNilai kembalian ganda dan data sementara yang dilewatkanData yang butuh invarian dan metode

Kelas menyimpan perilaku dan menjaga invarian, misalnya memastikan suhu tidak turun di bawah nol mutlak, sedangkan record hanya menyimpan nilai. Apabila data membutuhkan validasi atau metode, kelas adalah pilihan yang tepat. Apabila data hanya meneruskan beberapa nilai dari satu titik kode ke titik lain, record memberikan hasil yang sama dengan biaya deklarasi nol.

Ringkasan#

  • Literal record menulis nilai agregat tanpa deklarasi kelas, baik posisi seperti (3, 4) maupun bernama seperti (suhu: 30, tekanan: 1013).
  • Bidang posisi diakses dengan $1, $2, dan seterusnya, sedangkan bidang bernama diakses dengan namanya, dan tipe record mengikuti bentuknya.
  • Destrukturisasi memecah record menjadi variabel bernama dalam satu baris, terutama pada tipe kembalian fungsi.
  • Kesetaraan record bersifat struktural, yaitu bentuk sama ditambah semua bidang sama, sedangkan kelas membandingkan identitas kecuali operator == ditimpa.

Referensi#