Amalan Terbaik daripada “Effective Java (3rd Edition)”

Petikan daripada “Effective Java (3rd Edition) ” karya Joshua Bloch

1. Pulangkan koleksi atau tatasusunan kosong, bukan null

Jika kaedah memulangkan sebuah Set, jangan return null; untuk hasil yang kosong – sebaliknya gunakan return Set.of();.

2. Pertimbangkan kaedah kilang statik dan bukannya konstruktor

public static Boolean valueOf(boolean b) {
    return b ? Boolean.TRUE : Boolean.FALSE;
}

Tidak selalunya lebih baik, tetapi dalam sesetengah kes penggunaan ia menawarkan banyak kelebihan:

Kelemahan:

3. Pertimbangkan builder apabila berdepan dengan banyak parameter konstruktor

Kilang statik dan konstruktor berkongsi satu batasan: kedua-duanya tidak sesuai untuk bilangan parameter pilihan yang besar. Masalah ini sangat terasa dalam beberapa projek yang pernah saya sertai.

4. Kuatkuasakan sifat singleton dengan konstruktor persendirian atau jenis enum

Apa-apa sahaja yang tidak memerlukan berbilang instans patut diinstansiasikan sekali sahaja – sama ada dengan @ApplicationScoped atau corak singleton yang lain.

5. Kuatkuasakan sifat tidak boleh diinstansiasikan dengan konstruktor persendirian

Sesekali anda akan mahu menulis kelas yang hanya menghimpunkan kaedah statik dan medan statik. Kelas seperti ini mendapat reputasi buruk kerana ada orang menyalahgunakannya untuk mengelak daripada berfikir dari segi objek, tetapi ia memang mempunyai kegunaan yang sah.

private MyStaticClass() {
    // private constructor to prevent instantiation
}

6. Elakkan mencipta objek yang tidak perlu

String s = new String("bikini");  // DON'T DO THIS!

Atau:

// Hideously slow! Can you spot the object creation?
private static long sum() {
    Long sum = 0L;
    for (long i = 0; i <= Integer.MAX_VALUE; i++)
        sum += i;
    return sum;
}

Penyelesaian: Program ini memberikan jawapan yang betul, tetapi ia jauh lebih perlahan daripada sepatutnya, disebabkan satu kesilapan tipografi sebanyak satu aksara. Pemboleh ubah sum diisytiharkan sebagai Long dan bukannya long, yang bermakna program ini membina kira-kira 2^31 instans Long yang tidak perlu (lebih kurang satu bagi setiap kali long i ditambah kepada Long sum).

7. Elakkan finalizer dan cleaner

Finalizer tidak boleh diramal, sering berbahaya dan secara amnya tidak diperlukan. Penggunaannya boleh menyebabkan tingkah laku yang tidak menentu, prestasi yang lemah dan masalah kemudahalihan.

8. Utamakan try-with-resources berbanding try-finally

// try-with-resources - the best way to close resources!
try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
}

9. Pertimbangkan untuk melaksanakan Comparable

Dengan melaksanakan Comparable, anda membolehkan kelas anda bekerjasama dengan semua algoritma generik dan pelaksanaan koleksi yang bergantung pada antara muka ini.

10. Minimumkan kebolehcapaian kelas dan ahlinya

Satu-satunya faktor paling penting yang membezakan komponen yang direka dengan baik daripada yang direka dengan buruk ialah sejauh mana komponen itu menyembunyikan data dalamannya dan butiran pelaksanaan lain daripada komponen lain. Komponen yang direka dengan baik menyembunyikan semua butiran pelaksanaannya, dan memisahkan API daripada pelaksanaannya dengan kemas. Komponen-komponen kemudian berkomunikasi hanya melalui API masing-masing dan tidak mengetahui cara dalaman satu sama lain. Konsep ini, yang dikenali sebagai penyembunyian maklumat atau pengkapsulan, ialah prinsip asas reka bentuk perisian.

11. Dalam kelas public, gunakan kaedah pengakses, bukan medan public

Sesekali anda mungkin tergoda untuk menulis kelas yang merosot (degenerate), yang tiada tujuan lain selain menghimpunkan medan instans:

// Degenerate classes like this should not be public!
class Point {
    public double x;
    public double y;
}

Oleh sebab medan data kelas seperti ini diakses secara langsung, kelas ini tidak menawarkan faedah pengkapsulan. Anda tidak boleh mengubah perwakilannya tanpa mengubah API, anda tidak boleh menguatkuasakan invarian, dan anda tidak boleh mengambil tindakan tambahan apabila sesuatu medan diakses. Pengaturcara berorientasikan objek yang tegar berpendapat kelas seperti ini satu kesalahan besar dan sentiasa patut diganti dengan kelas yang mempunyai medan private dan kaedah pengakses public (getter) serta, bagi kelas yang boleh berubah, kaedah pengubah (setter):

// Encapsulation of data by accessor methods and mutators
class Point {

    private double x;
    private double y;

    public Point(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double getX() { return x; }
    public double getY() { return y; }

    public void setX(double x) { this.x = x; }
    public void setY(double y) { this.y = y; }
}

Sudah tentu, golongan tegar ini betul apabila melibatkan kelas public: jika sesebuah kelas boleh diakses di luar pakejnya, sediakan kaedah pengakses untuk mengekalkan keluwesan mengubah perwakilan dalaman kelas itu. Jika kelas public mendedahkan medan datanya, segala harapan untuk mengubah perwakilannya akan lenyap kerana kod klien mungkin sudah tersebar luas.

Walau bagaimanapun, jika sesebuah kelas ialah package-private atau kelas bersarang persendirian, tiada salahnya mendedahkan medan datanya – dengan andaian medan itu menggambarkan abstraksi yang disediakan oleh kelas tersebut dengan memadai.

12. Minimumkan kebolehubahan

Kelas immutable hanyalah kelas yang instansnya tidak boleh diubah suai. Semua maklumat dalam setiap instans tetap sepanjang hayat objek itu, jadi tiada perubahan yang akan dapat diperhatikan. Pustaka platform Java mengandungi banyak kelas immutable, termasuk String, kelas primitif berbungkus (boxed), serta BigInteger dan BigDecimal. Ada banyak sebab yang baik untuk ini: kelas immutable lebih mudah direka, dilaksanakan dan digunakan berbanding kelas yang boleh berubah. Ia kurang terdedah kepada ralat dan lebih selamat. Untuk menjadikan sesebuah kelas immutable, ikuti lima peraturan ini:

  1. Jangan sediakan kaedah yang mengubah suai keadaan objek (dikenali sebagai mutator).
  2. Pastikan kelas itu tidak boleh dilanjutkan. Ini menghalang subkelas yang cuai atau berniat jahat daripada menjejaskan sifat immutable kelas itu dengan berkelakuan seolah-olah keadaan objek telah berubah. Lazimnya, penyubkelasan dihalang dengan menjadikan kelas itu final, contohnya.
  3. Jadikan semua medan final. Ini menyatakan niat anda dengan jelas dalam bentuk yang dikuatkuasakan oleh sistem. Ia juga perlu untuk memastikan tingkah laku yang betul jika rujukan kepada instans yang baru dicipta dihantar dari satu thread ke thread lain tanpa penyegerakan, sebagaimana yang diperincikan dalam model memori.
  4. Jadikan semua medan private. Ini menghalang klien daripada mendapat akses kepada objek boleh ubah yang dirujuk oleh medan dan mengubah suai objek itu secara langsung. Walaupun dari segi teknikal kelas immutable dibenarkan mempunyai medan public final yang mengandungi nilai primitif atau rujukan kepada objek immutable, ini tidak disyorkan kerana ia menghalang perubahan perwakilan dalaman dalam keluaran akan datang.
  5. Pastikan akses eksklusif kepada sebarang komponen boleh ubah. Jika kelas anda mempunyai medan yang merujuk kepada objek boleh ubah, pastikan klien kelas itu tidak dapat memperoleh rujukan kepada objek tersebut. Jangan sekali-kali memulakan medan seperti ini dengan rujukan objek yang diberikan oleh klien, atau memulangkan medan itu daripada pengakses. Buat salinan defensif dalam konstruktor, pengakses dan kaedah readObject.

13. Utamakan komposisi berbanding pewarisan

Pewarisan ialah cara yang berkuasa untuk mencapai penggunaan semula kod, tetapi ia tidak selalunya alat terbaik untuk tugasan itu. Jika digunakan secara tidak wajar, ia menghasilkan perisian yang rapuh. Pewarisan selamat digunakan dalam satu pakej, apabila pelaksanaan subkelas dan superkelas berada di bawah kawalan pengaturcara yang sama. Ia juga selamat digunakan apabila melanjutkan kelas yang direka dan didokumenkan khusus untuk dilanjutkan. Namun, mewarisi kelas konkrit biasa merentasi sempadan pakej adalah berbahaya.

Tidak seperti panggilan kaedah, pewarisan melanggar pengkapsulan. Anda mungkin menyangka selamat untuk melanjutkan sesebuah kelas jika anda hanya menambah kaedah baharu dan tidak meng-override kaedah sedia ada. Walaupun lanjutan seperti ini jauh lebih selamat, ia bukan tanpa risiko. Jika superkelas mendapat kaedah baharu dalam keluaran berikutnya dan anda bernasib malang kerana telah memberikan subkelas satu kaedah dengan tandatangan yang sama tetapi jenis pulangan yang berbeza, subkelas anda tidak akan dapat dikompil lagi. Jika anda telah memberikan subkelas kaedah dengan tandatangan dan jenis pulangan yang sama seperti kaedah superkelas yang baharu itu, anda kini meng-override kaedah tersebut, jadi anda terdedah kepada masalah yang diterangkan sebelum ini. Tambahan pula, diragui sama ada kaedah anda akan memenuhi kontrak kaedah superkelas yang baharu itu, kerana kontrak tersebut belum ditulis semasa anda menulis kaedah subkelas.

Nasib baik, ada cara untuk mengelakkan semua masalah di atas. Daripada melanjutkan kelas sedia ada, berikan kelas baharu anda satu medan private yang merujuk kepada instans kelas sedia ada itu. Reka bentuk ini dipanggil komposisi kerana kelas sedia ada menjadi komponen kepada kelas baharu. Setiap kaedah instans dalam kelas baharu memanggil kaedah yang sepadan pada instans kelas sedia ada yang terkandung di dalamnya dan memulangkan hasilnya. Ini dikenali sebagai penerusan (forwarding), dan kaedah dalam kelas baharu dikenali sebagai kaedah penerus. Kelas yang terhasil akan sangat kukuh, tanpa sebarang kebergantungan pada butiran pelaksanaan kelas sedia ada. Malah, menambah kaedah baharu pada kelas sedia ada tidak akan memberi kesan kepada kelas baharu.

14. Utamakan antara muka berbanding kelas abstrak

Java mempunyai dua mekanisme untuk mentakrifkan jenis yang membenarkan berbilang pelaksanaan: antara muka dan kelas abstrak. Sejak kaedah lalai (default methods) untuk antara muka diperkenalkan dalam Java 8, kedua-dua mekanisme membolehkan anda menyediakan pelaksanaan bagi sesetengah kaedah instans.

Perbezaan utamanya ialah, untuk melaksanakan jenis yang ditakrifkan oleh kelas abstrak, sesebuah kelas mesti menjadi subkelas kepada kelas abstrak itu. Oleh sebab Java hanya membenarkan pewarisan tunggal, sekatan pada kelas abstrak ini sangat mengehadkan penggunaannya sebagai takrifan jenis. Mana-mana kelas yang mentakrifkan semua kaedah yang diperlukan dan mematuhi kontrak amnya dibenarkan melaksanakan sesebuah antara muka, tidak kira di mana kelas itu berada dalam hierarki kelas. Kelas sedia ada boleh diubah suai dengan mudah untuk melaksanakan antara muka baharu.

15. Generik: jangan gunakan jenis mentah

Anda tidak akan menemui ralat itu sehingga masa larian, lama selepas ia berlaku, dan dalam kod yang mungkin jauh daripada kod yang mengandungi ralat tersebut. Sebaik sahaja anda melihat ClassCastException, anda terpaksa menggeledah seluruh pangkalan kod untuk mencari panggilan kaedah yang memasukkan syiling ke dalam koleksi setem. Pengkompil tidak dapat membantu anda.

16. Generik: hapuskan amaran unchecked

Apabila anda memprogram dengan generik, anda akan melihat banyak amaran pengkompil:

Semakin banyak pengalaman anda dengan generik, semakin sedikit amaran yang anda terima, tetapi jangan harap kod yang baru ditulis akan dikompil tanpa sebarang amaran. Banyak amaran unchecked mudah dihapuskan. Contohnya, katakan anda tersilap menulis pengisytiharan ini:

Set<Lark> exaltation = new HashSet();

Tukarkannya kepada:

Set<Lark> exaltation = new HashSet<>();

Sesetengah amaran jauh lebih sukar dihapuskan. Apabila anda menerima amaran yang memerlukan pemikiran, teruskan berusaha! Hapuskan setiap amaran unchecked yang boleh anda hapuskan.

Jika anda menghapuskan semua amaran, anda terjamin bahawa kod anda selamat dari segi jenis (typesafe), dan itu perkara yang sangat baik. Ini bermakna anda tidak akan mendapat ClassCastException pada masa larian, dan ia menambah keyakinan anda bahawa program anda akan berkelakuan seperti yang dimaksudkan. Jika anda tidak dapat menghapuskan sesuatu amaran, tetapi anda boleh membuktikan bahawa kod yang mencetuskan amaran itu typesafe, barulah (dan hanya ketika itu) sekat amaran tersebut dengan anotasi @SuppressWarnings("unchecked"). Jika anda menyekat amaran tanpa terlebih dahulu membuktikan bahawa kod itu typesafe, anda hanya memberi diri sendiri rasa selamat yang palsu. Kod itu mungkin dikompil tanpa sebarang amaran, tetapi ia masih boleh melontarkan ClassCastException pada masa larian.

Setiap kali anda menggunakan anotasi @SuppressWarnings("unchecked"), tambahkan komen yang menerangkan mengapa ia selamat dilakukan.

17. Generik: apabila ia masuk akal

Aduh – sebuah kelas yang sangat memerlukan generik:

Contoh sebuah kelas yang memerlukan generik

Perkara seperti ini sepatutnya sudah dikesan paling lewat semasa merge request!

18. Generik: cara yang salah

Maaf, tiada kata-kata yang diperlukan. Tolong JANGAN SEKALI-KALI merge kod ini:

public abstract class JpaLearningCardBaseContentService<LC extends LearningCardBaseContent<L, LC, AC>, J extends JpaLearningCardBaseContent<L, LC, AC>, L extends
        LearningCardBase<LC, A>, A extends AnswerOptionBase<L, AC>, AC extends AnswerOptionBaseContent<LC, AC, A>, AS extends AnswerOptionBaseService<A, AC, L, LC>, ACS extends
        AnswerOptionBaseContentService<AC, A, L, LC>, LS extends LearningCardBaseService<L>>
    extends JpaLightPersistenceService<LC, J, Long> implements LearningCardBaseContentService<LC, L, A, AC> {
    // ...
}

Pengisytiharan sebuah kelas semata-mata tidak sepatutnya mengambil masa 5 minit untuk difahami.

19. Utamakan senarai berbanding tatasusunan

Tatasusunan berbeza daripada jenis generik dalam dua cara yang penting. Pertama, tatasusunan bersifat kovarian. Perkataan yang kedengaran menakutkan ini hanya bermaksud bahawa jika Sub ialah subjenis bagi Super, maka jenis tatasusunan Sub[] ialah subjenis bagi jenis tatasusunan Super[]. Sebaliknya, generik bersifat invarian: bagi mana-mana dua jenis berbeza Type1 dan Type2, List<Type1> bukan subjenis dan bukan juga superjenis bagi List<Type2>.

20. Utamakan lambda berbanding kelas tanpa nama

// Anonymous class instance as a function object - obsolete!
Collections.sort(words, new Comparator<String>() {
    public int compare(String s1, String s2) {
        return Integer.compare(s1.length(), s2.length());
    }
});

Sebaliknya, lakukan begini:

// Lambda expression as function object (replaces anonymous class)
Collections.sort(words, (s1, s2) -> Integer.compare(s1.length(), s2.length()));

21. Minimumkan skop pemboleh ubah setempat

Teknik paling berkesan untuk meminimumkan skop pemboleh ubah setempat ialah mengisytiharkannya di tempat ia mula-mula digunakan...

22. Utamakan gelung for-each berbanding gelung for tradisional

// The preferred idiom for iterating over collections and arrays
for (Element e : elements) {
    ... // Do something with e
}

23. Kenali dan gunakan pustaka

Katakan anda mahu menjana integer rawak antara sifar dan suatu had atas. Apabila berdepan dengan tugasan biasa ini, ramai pengaturcara akan menulis satu kaedah kecil yang kelihatan lebih kurang begini:

// Common but deeply flawed!
static Random rnd = new Random();

static int random(int n) {
    return Math.abs(rnd.nextInt()) % n;
}

Kaedah ini mungkin kelihatan baik, tetapi ia mempunyai tiga kelemahan. Yang pertama, jika n ialah kuasa dua yang kecil, jujukan nombor rawak akan berulang selepas tempoh yang agak singkat. Kelemahan kedua ialah jika n bukan kuasa dua, sesetengah nombor akan, secara purata, dipulangkan lebih kerap daripada yang lain. Jika n besar, kesan ini boleh menjadi agak ketara.

Kelemahan ketiga kaedah random ialah ia boleh, sesekali, gagal secara teruk dengan memulangkan nombor di luar julat yang ditetapkan. Ini kerana kaedah itu cuba memetakan nilai yang dipulangkan oleh rnd.nextInt() kepada int bukan negatif dengan memanggil Math.abs. Jika nextInt() memulangkan Integer.MIN_VALUE, maka Math.abs juga akan memulangkan Integer.MIN_VALUE, dan operator baki (%) akan memulangkan nombor negatif, dengan andaian n bukan kuasa dua. Ini hampir pasti akan menyebabkan program anda gagal, dan kegagalan itu mungkin sukar dihasilkan semula.

24. Elakkan float dan double jika jawapan yang tepat diperlukan

Gunakan BigDecimal untuk jumlah wang. float dan double menghasilkan ralat pembundaran.

Contohnya, katakan anda ada $1.03 dalam poket dan anda membelanjakan 42¢. Berapakah baki wang anda? Berikut ialah serpihan program naif yang cuba menjawab soalan ini:

System.out.println(1.03 - 0.42);

Malangnya, ia mencetak 0.6100000000000001. Ini bukan kes terpencil. Katakan anda ada satu dolar dalam poket, dan anda membeli sembilan sesendal (washer) berharga sepuluh sen setiap satu. Berapakah baki yang anda terima?

System.out.println(1.00 - 9 * 0.10);

Menurut serpihan program ini, anda menerima $0.09999999999999998.

Anda mungkin menyangka masalah ini boleh diselesaikan hanya dengan membundarkan hasil sebelum mencetaknya, tetapi malangnya, cara ini tidak selalu berkesan. Contohnya, katakan anda ada satu dolar dalam poket, dan anda melihat sebuah rak dengan sederet gula-gula lazat berharga 10¢, 20¢, 30¢ dan seterusnya, sehingga satu dolar. Anda membeli satu daripada setiap gula-gula, bermula dengan yang berharga 10¢, sehingga anda tidak mampu lagi membeli gula-gula seterusnya di rak itu.

25. Utamakan jenis primitif berbanding primitif berbungkus

// Broken comparator - can you spot the flaw?
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);

26. Rujuk objek melalui antara mukanya

Contoh yang baik:

Set<Son> sonSet = new LinkedHashSet<>();

Contoh yang buruk:

LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

27. Utamakan antara muka berbanding refleksi

Jika tidak:

28. Patuhi konvensyen penamaan yang diterima umum

Platform Java mempunyai set konvensyen penamaan yang mantap, kebanyakannya terkandung dalam The Java Language Specification [JLS, 6.1].

29. Gunakan pengecualian hanya untuk keadaan luar biasa

// Horrible abuse of exceptions. Don't ever do this!
try {
    int i = 0;
    while (true) {
        range[i++].climb();
    }
} catch (ArrayIndexOutOfBoundsException e) {
    // catch it
}

Apakah yang dilakukan oleh kod ini? Ia langsung tidak jelas apabila diteliti, dan itu sudah cukup menjadi sebab untuk tidak menggunakannya. Rupa-rupanya ia ialah idiom yang sangat tidak bijak untuk menggelung elemen sebuah tatasusunan. Gelung tak terhingga itu berakhir dengan melontar, menangkap dan mengabaikan ArrayIndexOutOfBoundsException apabila ia cuba mengakses elemen pertama di luar sempadan tatasusunan. Ia sepatutnya setara dengan idiom standard untuk menggelung tatasusunan, yang serta-merta dikenali oleh mana-mana pengaturcara Java:

for (Mountain m : range) {
    m.climb();
}

Pengecualian, seperti namanya, hanya untuk digunakan dalam keadaan luar biasa; ia tidak sepatutnya digunakan untuk aliran kawalan biasa.

30. Gunakan checked exception untuk keadaan yang boleh dipulihkan dan runtime exception untuk ralat pengaturcaraan

Gunakan checked exception untuk keadaan yang wajar dijangka boleh dipulihkan oleh pemanggil. Dengan menghadapkan pengguna kepada checked exception, pereka API memberikan mandat untuk pulih daripada keadaan itu. Pengguna boleh mengabaikan mandat ini dengan menangkap pengecualian itu dan tidak mempedulikannya, tetapi ini biasanya idea yang buruk.

Gunakan runtime exception untuk menandakan ralat pengaturcaraan. Sebahagian besar runtime exception menandakan pelanggaran prasyarat. Pelanggaran prasyarat hanyalah kegagalan klien sesebuah API untuk mematuhi kontrak yang ditetapkan oleh spesifikasi API itu. Contohnya, kontrak untuk akses tatasusunan menetapkan bahawa indeks tatasusunan mestilah antara sifar dan panjang tatasusunan tolak satu, termasuk kedua-duanya. ArrayIndexOutOfBoundsException menandakan bahawa prasyarat ini telah dilanggar.

31. Elakkan penggunaan checked exception yang tidak perlu

Ringkasnya, jika digunakan secara berhemah, checked exception boleh meningkatkan kebolehpercayaan program; jika digunakan secara berlebihan, ia menjadikan API menyakitkan untuk digunakan.

Pair<Boolean, LearningCardGroup> answerLearningCard(@NotNull LearningCardGroup learningCardGroup,
                                                      LearningCardAssignment learningCardAssignment,
                                                      @NotNull Collection<AnswerOption> answers)
      throws InvalidTrainingStateException, PersistenceException, LearningCardGroupAlreadyCompletedException,
             ConsecutiveCorrectCountExceededException,
             PreviousLearningCardGroupUncompletedException, LearningCardGroupCooldownException,
             LearningCardGroupHasUncompletedLearningCardsException, InvalidParticipantException, TrainingNotSignedException; // but why ???
     // this is a mistake seen a lot with junior devs
     // exceptions are exceptionally slow and should be avoided for mere validation
     // the performance gets even worse if a stacktrace is printed, since logging and printing is one of the slowest possible actions

32. Utamakan penggunaan pengecualian standard

Satu ciri yang membezakan pengaturcara pakar daripada yang kurang berpengalaman ialah pakar berusaha untuk mencapai, dan lazimnya berjaya mencapai, tahap penggunaan semula kod yang tinggi.

Pengecualian tidak terkecuali daripada peraturan bahawa penggunaan semula kod ialah perkara yang baik. Pustaka Java menyediakan satu set pengecualian yang merangkumi kebanyakan keperluan melontar pengecualian bagi kebanyakan API. Menggunakan semula pengecualian standard mempunyai beberapa faedah. Yang paling utama, ia menjadikan API anda lebih mudah dipelajari dan digunakan kerana ia sepadan dengan konvensyen mantap yang sudah biasa bagi pengaturcara. Yang hampir sama penting, program yang menggunakan API anda lebih mudah dibaca kerana ia tidak dipenuhi pengecualian yang asing. Akhir sekali (dan paling kecil kepentingannya), kelas pengecualian yang lebih sedikit bermakna penggunaan memori yang lebih kecil dan kurang masa dihabiskan untuk memuatkan kelas.

Jenis pengecualian yang paling kerap digunakan semula ialah IllegalArgumentException. Lazimnya, inilah pengecualian yang dilontar apabila pemanggil menghantar argumen yang nilainya tidak sesuai. Contohnya, inilah pengecualian yang patut dilontar jika pemanggil menghantar nombor negatif dalam parameter yang mewakili bilangan kali sesuatu tindakan perlu diulang. Satu lagi pengecualian yang kerap digunakan semula ialah IllegalStateException. Lazimnya, inilah pengecualian yang dilontar jika panggilan itu tidak sah disebabkan keadaan objek penerima. Contohnya, inilah pengecualian yang patut dilontar jika pemanggil cuba menggunakan sesuatu objek sebelum objek itu dimulakan dengan betul.

Boleh dikatakan, setiap panggilan kaedah yang salah akhirnya berpunca daripada argumen atau keadaan yang tidak sah, tetapi pengecualian lain lazimnya digunakan untuk jenis argumen dan keadaan tidak sah yang tertentu. Jika pemanggil menghantar null dalam parameter yang tidak membenarkan nilai null, konvensyen menetapkan bahawa NullPointerException dilontar dan bukannya IllegalArgumentException. Begitu juga, jika pemanggil menghantar nilai di luar julat dalam parameter yang mewakili indeks bagi suatu jujukan, IndexOutOfBoundsException patut dilontar dan bukannya IllegalArgumentException.

Jangan gunakan semula Exception, RuntimeException, Throwable atau Error secara langsung. Anggaplah kelas-kelas ini seolah-olah abstrak. Namun RuntimeException boleh digunakan jika anda mahu menukar checked exception kepada runtime exception, dan anda benar-benar tahu apa yang anda lakukan.

33. Jangan abaikan pengecualian

// Empty catch block ignores exception - Highly suspect!
try {
    ...
} catch (SomeException e) {
}

Blok catch yang kosong menggagalkan tujuan pengecualian, iaitu memaksa anda mengendalikan keadaan luar biasa. Mengabaikan pengecualian samalah seperti mengabaikan penggera kebakaran – lalu mematikannya supaya tiada orang lain berpeluang melihat sama ada benar-benar berlaku kebakaran.