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 bedaa.equals(c); // trueLiteral 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 berubahorder.items().add(new Item()); // ...tapi ini bisa saja berhasilList.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 cacheInteger p = 128, q = 128;p == q; // false — di luar cache, dua objekp.equals(q); // trueCache-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 exceptionMath.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:
- Override keduanya atau tidak sama sekali. Override
equalssaja berarti dua objek setara mendarat di bucket berbeda dan tidak ada yang bisa menemukan yang lain. - 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 baruvoid replace(User u) { u = new User("baru"); } // pemanggil tidak melihat apa punfinal 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.
Diskusi
Memuat komentar…