Java Modern untuk Produksi
EN
Materi 4 dari 9 · 14m

Objek, pewarisan, dan cara kegagalan menjalar

Kapan abstract class layak dipakai, kenapa komposisi biasanya menang, apa arti static sebenarnya, dan bedanya exception yang bisa ditindaklanjuti pemanggil dan yang tidak.

Interface atau abstract class

Interface mendeklarasikan kemampuan; abstract class membagi implementasi. Ambil abstract class hanya kalau ada state nyata atau algoritma nyata untuk dibagi — template method dengan hook, base yang memegang field yang dibutuhkan semua subclass. Selain itu interface, karena sebuah class bisa meng-implement banyak dan meng-extend tepat satu, dan slot satu itu layak dibiarkan kosong.

Default method mengaburkan ini, dan patokannya tetap: kalau kamu sedang menulis abstract class tanpa field dan tanpa konstruktor, yang kamu tulis itu interface.

Komposisi biasanya menang

Pewarisan mengikatmu ke implementasi superclass, bukan cuma kontraknya. Ubah si induk dan semua subclass berisiko — masalah fragile base class, dan alasan ArrayList bukan hal yang bagus untuk di-extend.

class AuditedRepo implements Repo { // komposisi
private final Repo delegate;
public void save(Order o) { audit(o); delegate.save(o); }
}

Class itu bisa membungkus Repo apa pun, bisa dites dengan fake, dan tidak bisa dirusak oleh perubahan di dalam delegate. Versi pewarisannya — class AuditedRepo extends JdbcRepo — bisa dirusak perubahan apa pun di JdbcRepo, termasuk yang cuma menambah pemanggilan internal antara dua method yang kamu override.

Overloading bukan overriding

  • Overloading: nama sama, tipe parameter beda, diselesaikan saat compile dari tipe statisnya. Dua method, tidak berhubungan saat runtime.
  • Overriding: signature sama di subtipe, diselesaikan saat runtime dari objek sebenarnya. Satu method, polimorfik.

Konsekuensi yang bikin orang tersandung: variabel List<String> yang memegang ArrayList memanggil overload yang dipilih untuk List, dan override yang didefinisikan ArrayList. Tipe menentukan overload; objek menentukan override.

static milik class

Anggota static ada satu kali, di class-nya, dan bisa dijangkau tanpa instance — jadi dia tidak bisa melihat field instance dan tidak bisa di-override (dia bisa disembunyikan, yang terlihat seperti override padahal bukan). State static yang mutable dibagi oleh setiap thread di proses itu, dan itu membuatnya cara paling sunyi untuk memasukkan race condition.

Dan aturan konstruktor yang sering mengecoh: class tanpa konstruktor mendapat satu konstruktor tanpa argumen secara implisit. Deklarasikan konstruktor apa pun, dan yang gratis itu hilang — itulah kenapa menambah konstruktor ber-argumen bisa merusak kode framework yang memanggil new Thing() secara reflektif.

Checked atau unchecked itu pertanyaan tentang si pemanggil

Beda mekanisnya: checked exception wajib dideklarasikan atau ditangkap. Pertanyaan desainnya lebih baik: apakah pemanggil bisa melakukan sesuatu yang berguna soal ini?

Situasi Jenis
Input user yang tidak valid checked, atau hasil validasi
Panggilan remote timeout dan retry mungkin menolong checked
Nilai config wajib tidak ada saat boot unchecked — gagal cepat
Bug: null di tempat yang dijamin non-null unchecked

Kalau pemanggil tidak punya respons yang berarti, checked exception hanya memaksa catch (Exception e) { throw new RuntimeException(e); } di setiap lapisan — tidak kehilangan apa pun, cuma menambah kebisingan. Membungkus itu wajar — selama cause-nya dibawa, karena stack trace tanpa penyebab aslinya adalah satu sore yang hilang.

finally selalu jalan, dan bisa memakan return-mu

int f() {
try { return 1; }
finally { System.out.println("selalu"); } // dicetak, lalu return 1
}

finally jalan saat return normal, saat exception, dan saat break/continue. Dia tidak jalan kalau JVM-nya keluar. Dan return di dalam finally menggantikan nilai atau exception dari try — menelan kegagalan aslinya dalam diam. Jangan pernah return dari finally.

Try-with-resources adalah alat yang lebih baik, dan menutup dalam urutan terbalik dari deklarasinya — itu penting kalau resource kedua bergantung pada yang pertama:

try (Connection c = open(); Statement s = c.createStatement()) {
...
} // s ditutup dulu, lalu c

NullPointerException itu fakta, bukan misteri

Artinya sebuah anggota diakses pada null. Di JVM modern pesannya menyebut ekspresinya — cukup membantu sehingga mematikan helpful NPE message jarang sepadan. Perbaikannya hampir selalu bukan null check di titik lemparnya; tapi menemukan boundary mana yang membiarkan null itu masuk, lalu menghentikannya di sana.


Lebih jauh soal penanganan error di boundary API: Bertahan di Produksi dengan Java.