Operator

Operator #

Operator adalah inti dari semua ekspresi — tanpa memahami cara kerjanya, kode yang tampak benar bisa menghasilkan hasil yang mengejutkan. Dart mewarisi sebagian besar operator dari keluarga C, tapi menambahkan beberapa operator yang menjadi keunggulan eksklusifnya: operator null-aware (??, ?., ??=), cascade (.., ?..), dan spread (...). Yang membedakan artikel ini dari sekadar daftar simbol: setiap grup operator dijelaskan dari sisi perilaku yang tidak obvious — preseden, short-circuit evaluation, perbedaan prefix vs postfix, dan kapan operator tertentu justru harus dihindari.

Preseden Operator #

Sebelum membahas operator satu per satu, penting memahami bahwa operator memiliki preseden (precedence) — urutan prioritas evaluasi ketika lebih dari satu operator muncul dalam satu ekspresi. Semakin tinggi preseden, semakin dulu dievaluasi.

Preseden Operator Deskripsi
16 (tertinggi) () [] ?. ! Akses, index, null-aware
15 ++ -- (postfix) Increment/decrement setelah
14 ! ~ - ++ -- (prefix) Unary, NOT, negasi, sebelum
13 * / ~/ % Perkalian dan pembagian
12 + - Penjumlahan dan pengurangan
11 << >> >>> Bit shift
10 & Bitwise AND
9 ^ Bitwise XOR
8 | Bitwise OR
7 >= > <= < as is is! Perbandingan dan type test
6 == != Kesetaraan
5 && Logika AND
4 || Logika OR
3 ?? Null-aware fallback
2 ?: Ternary kondisional
1 (terendah) = += -= dst, ??= Penugasan
// Contoh preseden yang sering membingungkan
int hasil = 2 + 3 * 4;     // 14, bukan 20 — * lebih tinggi dari +
bool cek = 5 > 3 && 2 < 4; // true — > dan < dievaluasi sebelum &&

// Gunakan kurung untuk kejelasan saat preseden tidak obvious
bool valid = (umur >= 18) && (saldo > 0) || darurat;
// vs
bool valid = (umur >= 18) && ((saldo > 0) || darurat); // makna berbeda!
// ANTI-PATTERN: mengandalkan preseden untuk ekspresi kompleks tanpa kurung
bool lolos = skor >= 70 && !diskualifikasi || hasilBanding;
// Apakah ini: (skor >= 70 && !diskualifikasi) || hasilBanding
// Atau:       skor >= 70 && (!diskualifikasi || hasilBanding)?

// BENAR: kurung eksplisit untuk setiap sub-ekspresi yang bermakna
bool lolos = (skor >= 70 && !diskualifikasi) || hasilBanding;

Operator Aritmatika #

Operator aritmatika bekerja pada tipe numerik (int, double, num). Satu hal yang membedakan Dart dari banyak bahasa lain: operator / selalu menghasilkan double, bukan int meski kedua operan adalah int.

int a = 17;
int b = 5;

print(a + b);   // 22
print(a - b);   // 12
print(a * b);   // 85
print(a / b);   // 3.4    — selalu double!
print(a ~/ b);  // 3      — pembagian bulat (floor division)
print(a % b);   // 2      — sisa bagi (modulo)
print(-a);      // -17    — negasi unary
// ANTI-PATTERN: menggunakan / dan berharap dapat int
int total = 100;
int bagian = total / 4;   // ✗ error: double tidak bisa diassign ke int
int bagian = total / 4;   // ✗ error kompilasi

// BENAR: gunakan ~/ untuk pembagian integer
int bagian = total ~/ 4;  // ✓ 25
// atau konversi eksplisit jika butuh double lalu int
int bagian = (total / 4).toInt(); // ✓ 25 (truncate)
int bagian = (total / 4).round(); // ✓ 25 (bulatkan ke terdekat)

Increment dan Decrement: Prefix vs Postfix #

Ini adalah salah satu sumber bug paling halus dalam semua bahasa pemrograman. Perbedaannya ada di kapan nilai dikembalikan relatif terhadap operasi:

int x = 5;

// Prefix — tambah DULU, kemudian kembalikan nilai baru
print(++x);  // 6  — x menjadi 6, lalu dicetak 6
print(x);    // 6

// Postfix — kembalikan nilai LAMA, kemudian tambah
x = 5;       // reset
print(x++);  // 5  — dicetak 5 (nilai lama), lalu x menjadi 6
print(x);    // 6
// ANTI-PATTERN: increment di dalam ekspresi yang bergantung pada nilainya
int i = 0;
int hasil = i++ + i++;   // ✗ hasil tidak jelas — bergantung pada urutan evaluasi
// Hindari menempatkan ++ atau -- dalam ekspresi yang kompleks

// BENAR: lakukan increment terpisah dari ekspresi
int i = 0;
int hasil = i + (i + 1); // niat jelas
i += 2;

Modulo dan Kasus Khusus Negatif #

Modulo (%) di Dart mengikuti tanda dari operan kiri (berbeda dari beberapa bahasa yang mengikuti tanda dari operan kanan):

print(7 % 3);    //  1 — positif % positif = positif
print(-7 % 3);   //  2 — Dart: tanda mengikuti operan KANAN (divisor)
print(7 % -3);   // -2 — tanda mengikuti operan KANAN
print(-7 % -3);  // -1 — keduanya negatif, hasil negatif

// Jika ingin selalu positif (misal untuk indeks sirkular):
int indeksSirkular(int n, int panjang) => ((n % panjang) + panjang) % panjang;
print(indeksSirkular(-1, 5));  // 4 — selalu dalam [0, panjang-1]

Operator Perbandingan #

Operator perbandingan selalu menghasilkan bool. Di Dart, == membandingkan nilai — bukan referensi memori (berbeda dari Java di mana == membandingkan referensi untuk objek).

int a = 10;
int b = 10;

print(a == b);   // true  — nilai sama
print(a != b);   // false
print(a > b);    // false
print(a < b);    // false
print(a >= b);   // true
print(a <= b);   // true

== pada Objek: Nilai vs Referensi #

// String — == membandingkan nilai (konten)
String s1 = 'halo';
String s2 = 'halo';
print(s1 == s2);        // true  — konten sama

// List — == membandingkan referensi, BUKAN konten
List<int> l1 = [1, 2, 3];
List<int> l2 = [1, 2, 3];
print(l1 == l2);        // false — dua objek berbeda di memori
print(identical(l1, l2)); // false — referensi berbeda

// Untuk membandingkan isi list, gunakan package collection atau listEquals
import 'package:collection/collection.dart';
print(const ListEquality().equals(l1, l2)); // true
// ANTI-PATTERN: menggunakan == untuk membandingkan List atau Map
List<String> pilihan = ['a', 'b', 'c'];
List<String> jawaban = ['a', 'b', 'c'];
if (pilihan == jawaban) {  // ✗ selalu false — membandingkan referensi
  print('Sama');
}

// BENAR: gunakan listEquals (dari Flutter) atau ListEquality (dari package collection)
import 'package:flutter/foundation.dart';
if (listEquals(pilihan, jawaban)) {  // ✓
  print('Sama');
}

Override == di Kelas Kustom #

Jika kelas kamu mewakili value object (objek yang identitasnya ditentukan oleh nilainya, bukan referensinya), kamu perlu meng-override == dan hashCode secara bersamaan:

class Koordinat {
  final double lat;
  final double lng;

  const Koordinat(this.lat, this.lng);

  // Wajib override hashCode jika override ==
  @override
  int get hashCode => Object.hash(lat, lng);

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is Koordinat && other.lat == lat && other.lng == lng;
  }
}

void main() {
  final a = Koordinat(-6.2, 106.8);
  final b = Koordinat(-6.2, 106.8);

  print(a == b);    // true  — setelah override
  print({a, b}.length); // 1 — Set menganggap keduanya sama
}
Jika kamu override ==, kamu harus juga override hashCode. Dua objek yang == harus menghasilkan hashCode yang sama — ini adalah kontrak yang digunakan oleh Set dan Map untuk efisiensi pencarian. Melanggar kontrak ini menyebabkan bug yang sangat sulit dilacak.

Operator Logika #

Operator logika bekerja pada nilai bool dan mengembalikan bool. Dart mengevaluasinya secara short-circuit — operan kanan hanya dievaluasi jika hasilnya belum bisa ditentukan dari operan kiri.

bool a = true;
bool b = false;

print(a && b);  // false — AND: keduanya harus true
print(a || b);  // true  — OR: minimal satu true
print(!a);      // false — NOT: balik nilai
print(a ^ b);   // true  — XOR: tepat satu yang true

Short-Circuit Evaluation #

Perilaku short-circuit bukan sekadar optimasi — ini adalah fitur yang bisa dan sering dimanfaatkan untuk keamanan:

// && short-circuit: jika kiri false, kanan TIDAK dievaluasi
String? nama;
// Aman — jika nama null, nama.isNotEmpty tidak pernah dipanggil
if (nama != null && nama.isNotEmpty) {
  print(nama.toUpperCase());
}

// || short-circuit: jika kiri true, kanan TIDAK dievaluasi
bool izin = sudahLogin || modeDemo();
// modeDemo() hanya dipanggil jika sudahLogin == false
// Berguna jika modeDemo() mahal atau memiliki efek samping

// Contoh penggunaan yang elegan
List<String>? items;
int panjang = items?.length ?? 0;
bool adaItem = items != null && items.isNotEmpty;
// ANTI-PATTERN: mengandalkan short-circuit untuk alur kontrol yang tidak obvious
bool sukses = simpanData() || tampilkanError();
// ✗ tampilkanError() hanya dipanggil jika simpanData() gagal — niat tersembunyi

// BENAR: gunakan if-else yang eksplisit untuk alur kontrol
bool sukses = simpanData();
if (!sukses) tampilkanError();

Operator Penugasan #

Dart menyediakan operator penugasan gabungan (compound assignment) untuk semua operator aritmatika dan bitwise, ditambah ??= yang spesifik untuk null safety.

int n = 10;

n += 5;    // n = n + 5  → 15
n -= 3;    // n = n - 3  → 12
n *= 2;    // n = n * 2  → 24
n ~/= 5;   // n = n ~/ 5 → 4  (pembagian integer)
n %= 3;    // n = n % 3  → 1
n <<= 2;   // n = n << 2 → 4  (shift kiri)
n >>= 1;   // n = n >> 1 → 2  (shift kanan)
n &= 3;    // n = n & 3  → 2  (bitwise AND)
n |= 5;    // n = n | 5  → 7  (bitwise OR)
n ^= 3;    // n = n ^ 3  → 4  (bitwise XOR)

// Khusus untuk nullable
String? pesan;
pesan ??= 'default';   // assign 'default' hanya jika pesan masih null
print(pesan);          // 'default'
pesan ??= 'lain';      // tidak melakukan apa-apa — pesan sudah 'default'
print(pesan);          // 'default'
// ANTI-PATTERN: menulis ulang variabel yang tidak perlu
jumlah = jumlah + tambahan;    // verbose
total = total - diskon;        // verbose

// BENAR: operator gabungan lebih ringkas dan ekspresif
jumlah += tambahan;
total -= diskon;

Operator Bitwise #

Operator bitwise bekerja langsung pada representasi biner dari int. Sangat berguna untuk manipulasi flag, encoding, masking, dan operasi performa tinggi.

int a = 0b1010;   // 10 dalam desimal
int b = 0b1100;   // 12 dalam desimal

print('a & b  = ${(a & b).toRadixString(2).padLeft(4,'0')}');  // 1000 = 8
print('a | b  = ${(a | b).toRadixString(2).padLeft(4,'0')}');  // 1110 = 14
print('a ^ b  = ${(a ^ b).toRadixString(2).padLeft(4,'0')}');  // 0110 = 6
print('~a     = ${(~a)}');   // -11 (komplemen dua)
print('a << 1 = ${(a << 1).toRadixString(2)}');  // 10100 = 20
print('a >> 1 = ${(a >> 1).toRadixString(2)}');  // 0101 = 5

Pola Umum: Bit Flag #

Bitwise paling sering digunakan untuk menyimpan beberapa boolean dalam satu integer menggunakan bit sebagai flag:

// Definisikan flag sebagai konstanta bit
class Izin {
  static const int baca   = 1 << 0;  // 0001 = 1
  static const int tulis  = 1 << 1;  // 0010 = 2
  static const int hapus  = 1 << 2;  // 0100 = 4
  static const int admin  = 1 << 3;  // 1000 = 8
}

// Gabungkan flag dengan |
int izinEditor = Izin.baca | Izin.tulis;          // 0011 = 3
int izinAdmin  = Izin.baca | Izin.tulis | Izin.hapus | Izin.admin; // 1111 = 15

// Cek flag dengan &
bool bisaBaca  = (izinEditor & Izin.baca)  != 0;  // true
bool bisaHapus = (izinEditor & Izin.hapus) != 0;  // false

// Tambah flag dengan |=
izinEditor |= Izin.hapus;    // tambahkan izin hapus

// Cabut flag dengan &= dan ~
izinEditor &= ~Izin.tulis;   // cabut izin tulis

Shift untuk Perkalian/Pembagian Cepat #

// Shift kiri = kali 2^n
int x = 5;
print(x << 1);   // 10  — sama dengan x * 2
print(x << 2);   // 20  — sama dengan x * 4
print(x << 3);   // 40  — sama dengan x * 8

// Shift kanan = bagi 2^n (floor division)
print(x >> 1);   // 2   — sama dengan x ~/ 2
print(x >> 2);   // 1   — sama dengan x ~/ 4

Operator Kondisional #

Dart menyediakan dua operator kondisional ringkas yang sering digunakan sebagai pengganti if-else sederhana.

Ternary: kondisi ? nilaijika : nilaiJikaTidak #

int umur = 20;
String status = umur >= 18 ? 'dewasa' : 'minor';
print(status); // dewasa

// Ternary bisa di-nest, tapi hati-hati keterbacaan
String kategori = umur < 13 ? 'anak'
    : umur < 18 ? 'remaja'
    : umur < 60 ? 'dewasa'
    : 'lansia';
// ANTI-PATTERN: ternary bersarang yang terlalu dalam
String hasil = a > b ? (a > c ? 'a terbesar' : 'c terbesar')
    : (b > c ? 'b terbesar' : 'c terbesar');
// ✗ sulit dibaca — lebih dari dua level nesting

// BENAR: gunakan if-else biasa atau fungsi terpisah
String tentukan(int a, int b, int c) {
  if (a > b && a > c) return 'a terbesar';
  if (b > c) return 'b terbesar';
  return 'c terbesar';
}

if-null: ekspresi ?? fallback #

Operator ?? mengembalikan operan kiri jika bukan null, atau operan kanan jika null. Ini adalah pengganti null-check yang sangat idiomatis di Dart:

String? nama;

// Dengan ternary (verbose)
String tampil = nama != null ? nama : 'Tamu';

// Dengan ?? (lebih ringkas dan idiomatis Dart)
String tampil = nama ?? 'Tamu';

// Dapat dirantai
String hasil = ambilDariCache() ?? ambilDariDatabase() ?? 'default';
// Mencoba cache dulu, jika null coba database, jika masih null gunakan default

Operator Null-Aware #

Ini adalah grup operator yang paling membedakan Dart dari bahasa lain dan paling berdampak dalam menangani null safety secara elegan.

class Pengguna {
  final String nama;
  final Alamat? alamat;  // nullable
  const Pengguna({required this.nama, this.alamat});
}

class Alamat {
  final String kota;
  final String? kodePos;  // juga nullable
  const Alamat({required this.kota, this.kodePos});
}

Pengguna? pengguna = ambilPengguna(); // bisa null

// ?. — safe navigation: akses properti/method hanya jika objek tidak null
String? namaKota = pengguna?.alamat?.kota;
// Jika pengguna null → null
// Jika alamat null → null
// Jika keduanya tidak null → nilai kota

// ?? — fallback: berikan default jika null
String kotaTampil = pengguna?.alamat?.kota ?? 'Kota tidak diketahui';

// ??= — assign jika null
pengguna ??= Pengguna(nama: 'Tamu');  // assign jika pengguna masih null

// ?.. — null-aware cascade
pengguna?..kirimNotifikasi()..simpanLog();
// Jika pengguna null, kedua method tidak dipanggil sama sekali
// ANTI-PATTERN: null check manual bertingkat yang verbose
String kotaTampil;
if (pengguna != null) {
  if (pengguna.alamat != null) {
    if (pengguna.alamat!.kota.isNotEmpty) {
      kotaTampil = pengguna.alamat!.kota;
    } else {
      kotaTampil = 'Tidak diketahui';
    }
  } else {
    kotaTampil = 'Tidak diketahui';
  }
} else {
  kotaTampil = 'Tidak diketahui';
}

// BENAR: operator null-aware meringkas semua kondisi di atas
String kotaTampil = pengguna?.alamat?.kota.isNotEmpty == true
    ? pengguna!.alamat!.kota
    : 'Tidak diketahui';

// Atau lebih sederhana:
String kotaTampil = (pengguna?.alamat?.kota ?? '').isEmpty
    ? 'Tidak diketahui'
    : pengguna!.alamat!.kota;

Operator Type Test: is, is!, dan as #

Operator ini bekerja bersama sistem tipe Dart untuk memeriksa dan mengonversi tipe secara aman.

Object nilai = 42;

// is — cek tipe, menghasilkan bool
print(nilai is int);     // true
print(nilai is String);  // false
print(nilai is num);     // true — int adalah subtipe num

// is! — negasi dari is
print(nilai is! String); // true

// as — cast paksa ke tipe tertentu
// Jika tipe tidak sesuai, throws CastError saat runtime
String teks = nilai as String;  // ✗ CastError — nilai adalah int, bukan String

Type Promotion Otomatis #

Fitur terbaik dari is: setelah berhasil, Dart secara otomatis memperlakukan variabel sebagai tipe yang diperiksa — tanpa perlu cast eksplisit:

void prosesNilai(Object nilai) {
  if (nilai is String) {
    // Di dalam blok ini, Dart tahu 'nilai' adalah String
    // Langsung bisa pakai semua method String tanpa cast!
    print(nilai.toUpperCase());   // ✓
    print(nilai.length);          // ✓
    print(nilai.split(','));       // ✓
  }

  if (nilai is int) {
    print(nilai * 2);             // ✓ langsung int
    print(nilai.isEven);          // ✓
  }

  if (nilai is List<String>) {
    print(nilai.first);           // ✓ langsung List<String>
    nilai.sort();                 // ✓
  }
}
// ANTI-PATTERN: cast langsung dengan as tanpa pengecekan
void proses(Object data) {
  String teks = data as String;  // ✗ crash jika data bukan String
  print(teks.length);
}

// BENAR: periksa dengan is dulu — type promotion menghilangkan kebutuhan as
void proses(Object data) {
  if (data is String) {
    print(data.length);  // ✓ type-safe, tidak perlu as
  } else {
    throw ArgumentError('Diharapkan String, dapat: ${data.runtimeType}');
  }
}

Cascade Operator: .. dan ?.. #

Cascade memungkinkan memanggil beberapa method atau mengakses beberapa properti pada objek yang sama dalam satu ekspresi berantai. Cascade tidak mengembalikan nilai terakhir — ia selalu mengembalikan objek aslinya.

// Tanpa cascade — verbose, mengulang nama variabel
var buffer = StringBuffer();
buffer.write('Halo');
buffer.write(', ');
buffer.write('Dart');
buffer.writeln('!');
print(buffer.toString()); // Halo, Dart!

// Dengan cascade — ringkas
var buffer = StringBuffer()
  ..write('Halo')
  ..write(', ')
  ..write('Dart')
  ..writeln('!');
print(buffer.toString()); // Halo, Dart!

Cascade sangat berguna untuk setup objek yang membutuhkan banyak konfigurasi:

class KonfigurasiServer {
  late String host;
  late int port;
  late bool ssl;
  late Duration timeout;
  late int maksKoneksi;

  void aturHost(String h) => host = h;
  void aturPort(int p) => port = p;
  void aktifkanSsl() => ssl = true;
  void aturTimeout(Duration d) => timeout = d;
  void aturMaksKoneksi(int n) => maksKoneksi = n;
}

// Tanpa cascade
var config = KonfigurasiServer();
config.aturHost('localhost');
config.aturPort(8080);
config.aktifkanSsl();
config.aturTimeout(Duration(seconds: 30));
config.aturMaksKoneksi(100);

// Dengan cascade — satu ekspresi yang kohesif
var config = KonfigurasiServer()
  ..aturHost('localhost')
  ..aturPort(8080)
  ..aktifkanSsl()
  ..aturTimeout(Duration(seconds: 30))
  ..aturMaksKoneksi(100);

?.. adalah versi null-aware dari cascade — seluruh chain diabaikan jika objek null:

Pengguna? pengguna = cariPengguna(id);

// ?.. — tidak melakukan apa-apa jika pengguna null
pengguna
  ?..kirimNotifikasi('Login berhasil')
  ..simpanLog(aksi: 'login')
  ..perbaruiWaktuAktif();

Spread Operator: ... dan ...? #

Spread memungkinkan menyisipkan semua elemen dari satu koleksi ke dalam koleksi lain — cara yang sangat ringkas untuk menggabungkan atau membangun koleksi dari bagian-bagiannya.

List<int> a = [1, 2, 3];
List<int> b = [4, 5, 6];

// Menggabungkan list
List<int> gabung = [...a, ...b];          // [1, 2, 3, 4, 5, 6]

// Sisipkan di tengah
List<int> dengan7 = [...a, 7, ...b];      // [1, 2, 3, 7, 4, 5, 6]

// Null-aware spread — diabaikan jika null
List<int>? opsional = null;
List<int> aman = [...a, ...?opsional, ...b]; // [1, 2, 3, 4, 5, 6]

// Spread juga bekerja untuk Set dan Map
Set<String> s1 = {'a', 'b'};
Set<String> s2 = {'c', 'd'};
Set<String> gabungSet = {...s1, ...s2}; // {'a', 'b', 'c', 'd'}

Map<String, int> m1 = {'a': 1, 'b': 2};
Map<String, int> m2 = {'c': 3, 'd': 4};
Map<String, int> gabungMap = {...m1, ...m2}; // {'a': 1, 'b': 2, 'c': 3, 'd': 4}
// ANTI-PATTERN: menggabungkan list dengan addAll yang verbose
List<String> hasil = [];
hasil.addAll(listA);
hasil.addAll(listB);
hasil.add('tambahan');
hasil.addAll(listC);

// BENAR: spread langsung di literal
List<String> hasil = [...listA, ...listB, 'tambahan', ...listC];

Mendefinisikan Operator di Kelas Kustom #

Dart memungkinkan kamu mendefinisikan ulang (override) sebagian besar operator untuk kelas yang kamu buat — ini disebut operator overloading. Ini membuat kode yang bekerja dengan tipe kustom terasa alami:

class Vektor {
  final double x;
  final double y;

  const Vektor(this.x, this.y);

  // Override operator +
  Vektor operator +(Vektor lain) => Vektor(x + lain.x, y + lain.y);

  // Override operator -
  Vektor operator -(Vektor lain) => Vektor(x - lain.x, y - lain.y);

  // Override operator * (perkalian skalar)
  Vektor operator *(double skalar) => Vektor(x * skalar, y * skalar);

  // Override operator == (wajib override hashCode juga)
  @override
  bool operator ==(Object other) =>
      other is Vektor && other.x == x && other.y == y;

  @override
  int get hashCode => Object.hash(x, y);

  // Override operator [] untuk akses komponen
  double operator [](int index) {
    if (index == 0) return x;
    if (index == 1) return y;
    throw RangeError('Index $index di luar rentang [0, 1]');
  }

  @override
  String toString() => 'Vektor($x, $y)';
}

void main() {
  final v1 = Vektor(1, 2);
  final v2 = Vektor(3, 4);

  print(v1 + v2);   // Vektor(4.0, 6.0)
  print(v2 - v1);   // Vektor(2.0, 2.0)
  print(v1 * 3);    // Vektor(3.0, 6.0)
  print(v1 == Vektor(1, 2)); // true
  print(v1[0]);     // 1.0
}

Operator yang bisa di-override di Dart:

<    >    <=    >=
-    +    /    ~/    *    %
|    ^    &
<<   >>   >>>
[]   []=
~    ==
// ANTI-PATTERN: operator overloading yang mengejutkan semantiknya
class Daftar {
  List<String> _items = [];

  // ✗ + yang menghapus item — melanggar ekspektasi pembaca
  Daftar operator +(String item) {
    _items.remove(item);  // mengejutkan! + tapi menghapus?
    return this;
  }
}

// BENAR: operator overloading harus intuitif dan mengikuti konvensi matematis
class Daftar {
  final List<String> _items;
  const Daftar(this._items);

  // + yang menambahkan — sesuai ekspektasi
  Daftar operator +(Daftar lain) => Daftar([..._items, ...lain._items]);
}

Ringkasan #

  • Preseden operator menentukan urutan evaluasi — gunakan kurung eksplisit untuk ekspresi kompleks agar niat kode tidak ambigu.
  • / selalu menghasilkan double — gunakan ~/ untuk pembagian integer, bukan (a / b).toInt() kecuali kamu memang perlu round/ceil/floor.
  • Prefix ++x vs postfix x++ — prefix menambah lalu mengembalikan nilai baru; postfix mengembalikan nilai lama lalu menambah. Hindari keduanya di dalam ekspresi yang bergantung pada nilainya.
  • == membandingkan nilai, bukan referensi, untuk semua tipe di Dart — tapi List dan Map tidak mengimplementasikan perbandingan isi secara default. Override == dan hashCode bersama-sama untuk value object kustom.
  • Short-circuit && dan || — operan kanan tidak dievaluasi jika hasilnya sudah pasti dari operan kiri. Manfaatkan ini untuk safe navigation, tapi jangan sembunyikan alur kontrol penting di dalamnya.
  • Operator null-aware (??, ?., ??=, ?..) adalah cara idiomatis Dart menangani nullable tanpa null check manual yang bertingkat-tingkat.
  • Type promotion setelah is — tidak perlu cast as setelah berhasil cek is. Dart otomatis memperlakukan variabel sebagai tipe yang sudah diverifikasi di dalam blok tersebut.
  • Cascade .. mengembalikan objek asli, bukan hasil method — ideal untuk setup objek dengan banyak konfigurasi.
  • Spread ... adalah cara paling ringkas menggabungkan atau membangun koleksi dari bagian-bagiannya — lebih ekspresif dari addAll() berulang.
  • Operator overloading memungkinkan tipe kustom terasa alami — tapi semantiknya harus intuitif dan mengikuti konvensi matematis. Jangan override + untuk melakukan penghapusan.

← Sebelumnya: Tipe Data   Berikutnya: Seleksi Kondisi →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact