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 overridehashCode. Dua objek yang==harus menghasilkanhashCodeyang sama — ini adalah kontrak yang digunakan olehSetdanMapuntuk 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 menghasilkandouble— gunakan~/untuk pembagian integer, bukan(a / b).toInt()kecuali kamu memang perlu round/ceil/floor.- Prefix
++xvs postfixx++— 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==danhashCodebersama-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 castassetelah berhasil cekis. 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 dariaddAll()berulang.- Operator overloading memungkinkan tipe kustom terasa alami — tapi semantiknya harus intuitif dan mengikuti konvensi matematis. Jangan override
+untuk melakukan penghapusan.