Operator #
Operator adalah simbol yang menginstruksikan compiler untuk melakukan operasi tertentu pada satu atau lebih nilai (operand). Java membagi operator ke dalam tujuh kategori berdasarkan fungsinya. Yang membedakan artikel ini dari sekadar daftar simbol: setiap kategori disertai dengan perilaku yang tidak intuitif dan anti-pattern yang sering menyebabkan bug — seperti integer division yang membuang desimal, perbedaan && vs &, dan jebakan operator ++ pre vs post increment. Memahami mengapa setiap operator berperilaku seperti itu jauh lebih berguna daripada sekadar tahu apa yang dilakukannya.
Operator Aritmatika #
Operator aritmatika melakukan operasi matematika dasar. Semua operator ini bekerja pada tipe numerik primitif (int, long, double, dll) dan menghasilkan nilai bertipe numerik.
| Operator | Nama | Contoh | Hasil |
|---|---|---|---|
+ | Penjumlahan | 10 + 3 | 13 |
- | Pengurangan | 10 - 3 | 7 |
* | Perkalian | 10 * 3 | 30 |
/ | Pembagian | 10 / 3 | 3 (bukan 3.33!) |
% | Sisa bagi (modulo) | 10 % 3 | 1 |
+ | Konkatenasi String | "a" + "b" | "ab" |
Jebakan Integer Division #
Pembagian dua int menghasilkan int — bagian desimal dibuang, bukan dibulatkan. Ini adalah salah satu sumber bug yang paling sering tidak disadari developer baru:
// ANTI-PATTERN: pembagian integer membuang desimal
int a = 10, b = 3;
double hasil = a / b; // 3.0 — bukan 3.333...!
System.out.println(a / b); // 3 — desimal dibuang
double rata = (1 + 2 + 3) / 3; // 2.0 — bukan 2.0!
// 1+2+3 = 6, lalu 6/3 = 2 (integer), baru dikonversi ke double
// BENAR: cast salah satu operan ke double dulu
double hasil = (double) a / b; // 3.3333...
double hasil = a / (double) b; // 3.3333...
double rata = (1 + 2 + 3) / 3.0; // 2.0 — benar
double rata = (double)(1 + 2 + 3) / 3; // 2.0 — benar
Modulo pada Bilangan Negatif #
% di Java mengikuti tanda dividend (bilangan yang dibagi), bukan divisor:
System.out.println( 7 % 3); // 1
System.out.println(-7 % 3); // -1 — negatif! (bukan 2)
System.out.println( 7 % -3); // 1
// Jika butuh hasil selalu positif (true modulo):
int hasil = ((−7) % 3 + 3) % 3; // 2 — selalu non-negatif
Operator Increment dan Decrement #
++ dan -- punya dua bentuk dengan perbedaan penting:
int x = 5;
// Post-increment: nilai lama digunakan dulu, LALU di-increment
int a = x++; // a = 5, kemudian x menjadi 6
System.out.println(a); // 5
System.out.println(x); // 6
// Pre-increment: di-increment dulu, LALU nilai baru digunakan
int b = ++x; // x menjadi 7 dulu, kemudian b = 7
System.out.println(b); // 7
System.out.println(x); // 7
// ANTI-PATTERN: ++/-- dalam ekspresi kompleks — sulit dibaca
int y = 5;
int z = y++ + ++y; // membingungkan: y=5, lalu 5 + 7 = 12? atau?
// BENAR: gunakan ++/-- sebagai statement tersendiri
y++;
z = y + y;
Operator Penugasan #
Operator penugasan menyimpan nilai ke variabel. Semua operator compound assignment (+=, -=, dll) melakukan operasi sekaligus menyimpan hasilnya — dan secara implisit melakukan narrowing cast yang bisa mengejutkan.
| Operator | Setara dengan | Contoh |
|---|---|---|
= | — | x = 10 |
+= | x = x + n | x += 5 |
-= | x = x - n | x -= 3 |
*= | x = x * n | x *= 2 |
/= | x = x / n | x /= 4 |
%= | x = x % n | x %= 3 |
&= | x = x & n | x &= 0xFF |
|= | x = x | n | x |= 0x01 |
^= | x = x ^ n | x ^= mask |
<<= | x = x << n | x <<= 2 |
>>= | x = x >> n | x >>= 1 |
Compound Assignment dan Implicit Cast #
Operator compound assignment menyertakan narrowing cast implisit yang tidak dimiliki ekuivalennya secara manual:
byte b = 10;
// Ini COMPILE ERROR — int tidak bisa otomatis masuk ke byte
b = b + 1; // ✗ ERROR: possible lossy conversion from int to byte
// Ini BENAR — += menyertakan cast implisit
b += 1; // ✓ setara dengan b = (byte)(b + 1)
// Implikasi: compound assignment bisa menyebabkan narrowing diam-diam
byte nilai = 100;
nilai += 100; // nilai = (byte)(100 + 100) = (byte)200 = -56! (overflow)
Operator Pembanding #
Operator pembanding membandingkan dua nilai dan selalu menghasilkan boolean. Digunakan sebagai kondisi di if, while, for, dan ekspresi boolean lainnya.
| Operator | Makna | Contoh | Hasil |
|---|---|---|---|
== | Sama dengan | 5 == 5 | true |
!= | Tidak sama dengan | 5 != 3 | true |
> | Lebih besar | 5 > 3 | true |
< | Lebih kecil | 5 < 3 | false |
>= | Lebih besar atau sama | 5 >= 5 | true |
<= | Lebih kecil atau sama | 5 <= 4 | false |
==pada tipe referensi membandingkan alamat, bukan nilai. Ini sudah dibahas di artikel Tipe Data, tapi layak diulang karena sangat sering menyebabkan bug:String s1 = new String("halo"); String s2 = new String("halo"); s1 == s2; // false — alamat memori berbeda s1.equals(s2); // true — isi sama // Khusus null-safe comparison, gunakan Objects.equals: Objects.equals(s1, null); // false, tanpa NullPointerException Objects.equals(null, s2); // false, aman
Operator Logika #
Operator logika menggabungkan dua ekspresi boolean menjadi satu hasil boolean. Ada dua versi: short-circuit (&&, ||) dan non-short-circuit (&, |).
| Operator | Nama | Deskripsi |
|---|---|---|
&& | AND (short-circuit) | true jika kedua operan true; berhenti di kiri jika kiri false |
|| | OR (short-circuit) | true jika salah satu true; berhenti di kiri jika kiri true |
! | NOT | Membalik nilai boolean |
& | AND (non-short-circuit) | Evaluasi kedua sisi selalu |
| | OR (non-short-circuit) | Evaluasi kedua sisi selalu |
^ | XOR | true jika tepat satu operan true |
Short-Circuit Evaluation #
Ini adalah perilaku penting yang sering dimanfaatkan di Java:
// && berhenti di operan kiri jika hasilnya sudah pasti false
String nama = null;
if (nama != null && nama.length() > 0) { // ✓ aman
System.out.println(nama);
// jika nama null, nama.length() tidak pernah dipanggil
}
// ANTI-PATTERN: tanpa null check di kiri
if (nama.length() > 0 && nama != null) { // ✗ NullPointerException jika nama null!
// || berhenti di operan kiri jika hasilnya sudah pasti true
int cache = -1;
int nilai = (cache != -1) || hitungNilaiMahal(); // hitungNilaiMahal() tidak dipanggil jika cache valid
flowchart LR
subgraph "&& Short-Circuit"
A["Evaluasi\noperan kiri"] --> B{Kiri == false?}
B -- Ya --> C["Kembalikan false\nKanan TIDAK dievaluasi"]
B -- Tidak --> D["Evaluasi\noperan kanan"] --> E["Kembalikan\nnilai kanan"]
endflowchart LR
subgraph "|| Short-Circuit"
A["Evaluasi\noperan kiri"] --> B{Kiri == true?}
B -- Ya --> C["Kembalikan true\nKanan TIDAK dievaluasi"]
B -- Tidak --> D["Evaluasi\noperan kanan"] --> E["Kembalikan\nnilai kanan"]
end&& vs & — Kapan Pakai Yang Mana
#
// Gunakan && (short-circuit) untuk kondisi normal
// Operan kanan tidak dievaluasi jika tidak perlu
if (list != null && list.size() > 0) { }
// Gunakan & (non-short-circuit) HANYA jika operan kanan harus selalu dieksekusi
// karena punya efek samping yang dibutuhkan
if (validasiField1() & validasiField2()) {
// kedua validasi selalu berjalan, error messages dari keduanya terkumpul
}
Operator Bitwise #
Operator bitwise bekerja langsung pada level bit integer. Paling umum digunakan untuk flag bitmask, operasi performa tinggi, atau saat berinteraksi dengan protokol biner.
| Operator | Nama | Deskripsi |
|---|---|---|
& | AND | Bit 1 hanya jika kedua bit 1 |
| | OR | Bit 1 jika salah satu bit 1 |
^ | XOR | Bit 1 jika tepat satu bit 1 |
~ | NOT (komplemen) | Membalik semua bit |
<< | Left shift | Geser bit ke kiri, isi 0 di kanan |
>> | Signed right shift | Geser ke kanan, pertahankan sign bit |
>>> | Unsigned right shift | Geser ke kanan, isi 0 di kiri |
int a = 0b1010; // 10 dalam desimal
int b = 0b1100; // 12 dalam desimal
System.out.println(a & b); // 0b1000 = 8 (AND)
System.out.println(a | b); // 0b1110 = 14 (OR)
System.out.println(a ^ b); // 0b0110 = 6 (XOR)
System.out.println(~a); // -11 (NOT: flip semua bit + sign)
// Shift: perkalian/pembagian dengan pangkat 2 yang lebih cepat
int x = 4;
System.out.println(x << 2); // 16 (x * 4)
System.out.println(x >> 1); // 2 (x / 2)
// Bitmask — simpan banyak flag dalam satu int
final int FLAG_BACA = 0b001; // 1
final int FLAG_TULIS = 0b010; // 2
final int FLAG_EKSEKUSI = 0b100; // 4
int izin = FLAG_BACA | FLAG_TULIS; // izin = 0b011 = 3
// Cek apakah flag tertentu aktif
boolean bisaBaca = (izin & FLAG_BACA) != 0; // true
boolean bisaEksek = (izin & FLAG_EKSEKUSI) != 0; // false
// Tambah flag
izin |= FLAG_EKSEKUSI; // izin = 0b111 = 7
// Hapus flag
izin &= ~FLAG_TULIS; // izin = 0b101 = 5
Operator Ternary #
Operator ternary ? : adalah ekspresi kondisional tiga operan — satu-satunya operator Java dengan tiga bagian. Ia menghasilkan nilai (bukan statement), jadi bisa digunakan di mana pun ekspresi diharapkan.
// Sintaks: kondisi ? nilaiJikaTrue : nilaiJikaFalse
int maks = (a > b) ? a : b;
String status = (umur >= 18) ? "dewasa" : "anak-anak";
// Berguna untuk nilai default
String nama = (input != null) ? input : "Anonim";
// Atau gunakan ekuivalennya yang lebih bersih:
String nama = Objects.requireNonNullElse(input, "Anonim");
Ternary vs if-else — Kapan Pakai Yang Mana #
// BENAR: ternary untuk ekspresi sederhana satu baris
int abs = (x >= 0) ? x : -x;
String label = aktif ? "Aktif" : "Nonaktif";
// ANTI-PATTERN: ternary bersarang — sangat sulit dibaca
String kategori = (nilai >= 90) ? "A"
: (nilai >= 80) ? "B"
: (nilai >= 70) ? "C"
: (nilai >= 60) ? "D" : "E"; // ✗ gunakan if-else atau switch
// BENAR: if-else untuk logika yang kompleks atau bersarang
String kategori;
if (nilai >= 90) kategori = "A";
else if (nilai >= 80) kategori = "B";
else if (nilai >= 70) kategori = "C";
else if (nilai >= 60) kategori = "D";
else kategori = "E";
Operator instanceof
#
instanceof memeriksa apakah sebuah objek adalah instance dari tipe tertentu. Hasilnya boolean. Sejak Java 16, ada pattern matching instanceof yang menggabungkan pemeriksaan tipe dan casting dalam satu langkah.
// Cara lama — periksa lalu cast secara terpisah
Object obj = "Halo Java";
if (obj instanceof String) {
String s = (String) obj; // cast manual
System.out.println(s.length());
}
// Cara baru (Java 16+) — pattern matching: periksa + bind sekaligus
if (obj instanceof String s) { // s otomatis bertipe String dalam blok ini
System.out.println(s.length()); // tidak perlu cast manual
}
// Pattern matching membuat kode jauh lebih ringkas untuk polymorphism
void proses(Object bentuk) {
if (bentuk instanceof Lingkaran c) {
System.out.println("Jari-jari: " + c.getJariJari());
} else if (bentuk instanceof Persegi p) {
System.out.println("Sisi: " + p.getSisi());
} else if (bentuk instanceof Segitiga s) {
System.out.println("Alas: " + s.getAlas());
}
}
// instanceof selalu false untuk null
String s = null;
System.out.println(s instanceof String); // false — tidak throw NPE
Precedence (Urutan Prioritas) #
Ketika beberapa operator muncul dalam satu ekspresi, Java mengevaluasinya berdasarkan precedence — urutan prioritas dari tinggi ke rendah:
| Prioritas | Operator | Asosiativitas |
|---|---|---|
| 1 (tertinggi) | ++ -- (post), (), [], . | Kiri ke kanan |
| 2 | ++ -- (pre), + - (unary), ~, ! | Kanan ke kiri |
| 3 | * / % | Kiri ke kanan |
| 4 | + - | Kiri ke kanan |
| 5 | << >> >>> | Kiri ke kanan |
| 6 | < > <= >= instanceof | Kiri ke kanan |
| 7 | == != | Kiri ke kanan |
| 8 | & | Kiri ke kanan |
| 9 | ^ | Kiri ke kanan |
| 10 | | | Kiri ke kanan |
| 11 | && | Kiri ke kanan |
| 12 | || | Kiri ke kanan |
| 13 | ? : (ternary) | Kanan ke kiri |
| 14 (terendah) | = += -= dll | Kanan ke kiri |
// Contoh precedence dalam praktik
int hasil = 2 + 3 * 4; // 14 — bukan 20: * lebih tinggi dari +
int hasil = (2 + 3) * 4; // 20 — kurung memaksa urutan
boolean cek = 5 > 3 && 2 < 4; // true: > dan < dievaluasi dulu, baru &&
// ANTI-PATTERN: mengandalkan precedence untuk ekspresi kompleks
int x = a++ * b-- + c >> 2; // ✗ sangat sulit dibaca
// BENAR: gunakan kurung untuk memperjelas urutan yang dimaksud
int x = ((a++) * (b--) + c) >> 2; // ✓ niat jelas
Ringkasan #
- Integer division membuang desimal —
7 / 2menghasilkan3, bukan3.5; cast kedoublesalah satu operan dulu:(double) 7 / 2atau7 / 2.0.&&dan||adalah short-circuit — operan kanan tidak dievaluasi jika hasil sudah pasti; manfaatkan ini untuk null-check:obj != null && obj.metode().==pada referensi membandingkan alamat — gunakan.equals()untuk isi objek danObjects.equals()untuk null-safe comparison.- Compound assignment menyertakan implicit cast —
b += 1tidak sama persis denganb = b + 1untukbyte/short; yang pertama menyertakan narrowing cast otomatis.- Pre vs post increment berbeda —
a++menggunakan nilai lama lalu increment;++aincrement dulu lalu gunakan nilai baru; hindari keduanya dalam ekspresi kompleks.instanceofpattern matching (Java 16+) —if (obj instanceof String s)menggabungkan pemeriksaan tipe dan cast dalam satu langkah;instanceofselalufalseuntuknull.- Ternary untuk ekspresi sederhana saja —
kondisi ? a : bbaik untuk nilai default atau pilihan satu baris; hindari ternary bersarang, gunakanif-elseuntuk logika kompleks.- Gunakan kurung untuk kejelasan — jangan andalkan precedence untuk ekspresi yang punya lebih dari dua operator; kurung membuat niat lebih jelas bagi pembaca.