제이온

[Effective Java] Item 6. Hindari Pembuatan Objek yang Tidak Diperlukan

  • Bahasa Penulisan: Bahasa Korea
  • Negara Standar: Semua Negaracountry-flag
  • TI

Dibuat: 2024-04-28

Dibuat: 2024-04-28 13:40

Ketika Membuat Objek yang Tidak Diperlukan

Menggunakan `new String()`


String a, b, dan c semuanya akan memiliki nilai string “hi”. Namun, ketiga string ini merujuk ke alamat yang berbeda, yang berarti alokasi memori yang berbeda dilakukan untuk data yang sama, yang mengakibatkan pemborosan.


<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2Fa53d14aa-6abf-40f0-8441-435647d172fa%2FUntitled.png?table=block&id=f168859c-367c-4924-a4c6-5e04788fab67&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1146;" width="2000" height="1146"></span>


Oleh karena itu, saat mendeklarasikan string, jangan gunakan kata kunci `new`, tetapi gunakan literal.



Kode di atas hanya menggunakan satu instance. Lebih jauh lagi, dengan menggunakan metode ini, kita dapat memastikan bahwa semua kode yang menggunakan literal string “hi” di dalam JVM yang sama akan menggunakan objek yang sama. Ini disebabkan oleh fitur Java Constant Pool.


Menggunakan `new Boolean()`

Kode di atas membuat instance Boolean menggunakan konstruktor yang menerima string sebagai parameter. Boolean hanya memiliki dua nilai, yaitu `true` atau `false`, jadi membuat instance setiap kali akan menjadi pemborosan memori. Oleh karena itu, lebih baik menggunakan metode pabrik statis `Boolean.valueOf()`.



Menggunakan `String.matches()`

Jika biaya pembuatannya tinggi, lebih baik menyimpannya dalam cache dan menggunakannya kembali. Namun, kita tidak selalu tahu biaya pembuatan objek yang kita buat. Misalnya, jika kita ingin membuat metode untuk memeriksa apakah string yang diberikan adalah angka Romawi yang valid, menggunakan ekspresi reguler adalah cara termudah.



Namun, `String.matches()` adalah metode yang bermasalah dari segi performa. Instance `Pattern` untuk ekspresi reguler yang dibuat oleh metode ini digunakan sekali lalu dibuang dan menjadi target garbage collection. Semakin sering ekspresi reguler ini digunakan, semakin besar biaya pembuatan dan pembuangan instance `Pattern` yang sama. Oleh karena itu, lebih baik menyimpan instance `Pattern` di dalam cache dan menggunakannya kembali setiap kali metode `isRomanNumeral()` dipanggil.



Catatan

Dalam semua contoh di atas, ketika menyimpan objek yang tidak diperlukan, kita selalu membuatnya menjadi objek yang tidak berubah. Ini karena itu aman untuk digunakan kembali. Namun, ada kalanya intuisi kita bertentangan dengan penggunaan kembali objek yang tidak berubah.


Adapter (view) adalah objek yang mendelegasikan pekerjaan sebenarnya ke objek back-end dan bertindak sebagai antarmuka kedua. Adapter hanya perlu mengelola objek back-end, jadi kita hanya perlu membuat satu adapter untuk setiap objek back-end.


Misalnya, metode `keySet()` dari antarmuka `Map` mengembalikan view `Set` yang berisi semua kunci di dalam objek `Map`. Pengguna mungkin berpikir bahwa instance `Set` baru dibuat setiap kali metode `keySet()` dipanggil, tetapi jika kita melihat implementasi JDK, kita akan melihat bahwa instance `Set` yang dapat berubah yang sama dikembalikan setiap kali.


Ini karena fungsi yang dilakukan oleh semua instance `Set` sama, dan semua instance `Set` mewakili instance `Map`. Jadi, meskipun `keySet()` membuat beberapa objek view, tidak masalah dan tidak ada manfaatnya.



Oleh karena itu, jika kita memodifikasi instance `names1`, instance `names2` juga akan terpengaruh.


Namun, secara pribadi, saya percaya bahwa nilai balik dari metode `keySet()` harus menggunakan defensive copy untuk mengembalikan objek baru setiap kali. Jika instance `Set` yang diterima dari metode `keySet()` juga digunakan di tempat lain, dan ada kode yang mengubah status instance ini, kita tidak dapat yakin dengan nilai instance `Set` dan `Map` yang sedang digunakan.


Selain itu, kecuali dalam lingkungan di mana `keySet()` digunakan secara berlebihan, pembuatan instance `Set` setiap kali tidak akan berdampak signifikan pada performa. Sebaliknya, membuat instance `Set` menjadi objek yang tidak berubah akan lebih baik untuk pemeliharaan yang stabil.


Autoboxing

Autoboxing adalah teknik yang secara otomatis mengubah tipe primitif dan tipe wrapper saat programmer mencampurnya. Namun, autoboxing hanya mengaburkan perbedaan antara tipe primitif dan tipe wrapper, tidak menghilangkannya sepenuhnya.



Secara logika tidak ada masalah, tetapi kode ini sangat tidak efisien dari segi performa. Penyebabnya adalah tipe `sum` dan tipe `i` di dalam loop `for`.


Tipe `sum` adalah `Long`, dan `i` adalah `long`. Dengan kata lain, `long` `i` akan membuat instance `Long` baru setiap kali ditambahkan ke `sum` di dalam loop. Akibatnya, lebih baik menggunakan tipe primitif daripada tipe wrapper dan berhati-hatilah agar autoboxing tidak terjadi secara tidak sengaja.


Hal yang Tidak Boleh Disalahpahami

Jangan salah mengartikan anjuran untuk menghindari pembuatan objek yang tidak diperlukan sebagai berarti kita harus menghindarinya karena biayanya yang besar.


Terutama di JVM modern, pembuatan dan pengumpulan objek kecil yang tidak perlu bukanlah tugas yang berat. Jadi, kecuali untuk objek dengan biaya pembuatan yang sangat tinggi seperti koneksi database, jangan buat custom object pool.


Lebih jauh lagi, ingatlah bahwa dampak dari kegagalan defensive copy saat menggunakan kembali objek jauh lebih besar daripada dampak dari pembuatan objek yang tidak perlu secara berulang. Dampak dari pembuatan berulang hanya memengaruhi bentuk dan performa kode, tetapi kegagalan defensive copy akan langsung menyebabkan bug dan masalah keamanan.


Sumber

Komentar0