Java Modern untuk Produksi
EN
Materi 3 dari 9 · 15m

Nilai, identitas, dan kesetaraan

Kenapa == pada String mengkhianatimu, apa bedanya int dan Integer sebenarnya, kontrak key HashMap, dan immutability yang cuma sebatas kulit.

Sebagian besar perilaku Java yang bikin bingung berasal dari satu pembedaan: sebuah variabel menyimpan nilai atau referensi, dan == selalu membandingkan apa yang disimpan variabel itu.

== pada objek membandingkan referensi

String a = "hello";
String b = "hello";
a == b; // true — keduanya menunjuk literal ter-intern yang sama
String c = new StringBuilder("hel").append("lo").toString();
a == c; // false — karakternya sama, objeknya beda
a.equals(c); // true

Literal di-intern ke pool bersama saat class dimuat, dan itulah yang membuat perbandingan pertama mengecoh orang jadi berpikir == bekerja untuk string. Apa pun yang dibangun saat runtime adalah objek baru. Pakai equals untuk teks, selalu; == untuk teks adalah bug yang lolos test-nya sendiri, karena test-nya memakai literal.

Immutable berarti objeknya, bukan yang ditunjuknya

String itu immutable: tidak ada method yang mengubahnya, setiap “perubahan” mengembalikan yang baru. Itu sebabnya dia aman dibagi antar thread dan aman jadi key map.

Kata yang sama dipakai longgar di tempat lain, dan di situ cuma sebatas kulit:

List<String> scopes = List.of("read", "write");
scopes.add("admin"); // UnsupportedOperationException
record Order(List<Item> items) {} // referensinya tidak bisa berubah
order.items().add(new Item()); // ...tapi ini bisa saja berhasil

List.of memberi list yang benar-benar tidak bisa diubah — dan dia juga menolak elemen null, yang tidak dilakukan Arrays.asList. Collections.unmodifiableList(x) itu view: ubah x dan list yang “unmodifiable” itu berubah di bawahmu. List.copyOf menyalin lebih dulu, dan itulah yang kamu mau di dalam konstruktor.

int dan Integer beda lebih dari sekadar sintaks

int itu primitif: sebuah nilai, tidak pernah null, dibandingkan dengan ==. Integer itu objek: bisa null, di-box di heap, dan dibandingkan == sebagai referensi.

Integer x = 127, y = 127;
x == y; // true — nilai kecil berasal dari cache
Integer p = 128, q = 128;
p == q; // false — di luar cache, dua objek
p.equals(q); // true

Cache-nya mencakup −128..127 secara default. Ini sebabnya == pada Integer adalah bug yang berhasil saat dites dengan angka kecil dan gagal di produksi dengan angka sungguhan. Dan Integer yang null akan melempar NullPointerException begitu di-unbox menjadi int — termasuk di dalam aritmetika dan di dalam switch.

Overflow terjadi diam-diam, bukan jadi error:

int max = Integer.MAX_VALUE;
max + 1; // -2147483648, berputar, tanpa exception

Math.addExact melempar exception — dan itu yang kamu mau untuk apa pun yang menghitung uang.

Kontrak key HashMap

Untuk bisa jadi key, sebuah tipe harus mengimplementasikan equals dan hashCode secara konsisten: objek yang setara wajib mengembalikan hash code yang setara. Rusak di salah satu arah, dan lookup-nya gagal dalam diam, bukan dengan keras.

Dua aturan yang mengikutinya, dan yang kedua menggigit:

  1. Override keduanya atau tidak sama sekali. Override equals saja berarti dua objek setara mendarat di bucket berbeda dan tidak ada yang bisa menemukan yang lain.
  2. Key harus efektif immutable pada field yang menyusun hash-nya. Ubah satu setelah dimasukkan, dan entry-nya terlantar — ada di dalam map, tidak terjangkau get, tapi masih terlihat saat iterasi. Record memudahkan hal ini dilakukan dengan benar, selama komponen koleksinya disalin di konstruktor.

Pass-by-value, termasuk referensinya

Java mengirim semuanya by value. Untuk objek, nilai yang dikirim adalah referensinya — jadi method itu bisa mengubah objeknya, dan tidak bisa mengubah objek mana yang ditunjuk variabel si pemanggil.

void rename(User u) { u.setName("baru"); } // pemanggil melihat nama baru
void replace(User u) { u = new User("baru"); } // pemanggil tidak melihat apa pun

final pada field berarti referensinya tidak bisa di-assign ulang. Dia tidak mengatakan apa pun soal apakah objek yang ditunjuknya bisa berubah — final List<String> tags tetap bisa ditambahi sepanjang hari.


Di mana ini menggigit dalam praktik, termasuk di boundary JPA dan Jackson: Pola Java Modern untuk Produksi.