“Effective Java (3rd Edition)” kitabından en iyi uygulamalar

Joshua Bloch’un “Effective Java (3rd Edition) ” kitabından alıntılar

1. Null yerine boş collection veya dizi döndürün

Metot bir Set döndürüyorsa, boş bir sonuç için return null; yazmayın – bunun yerine return Set.of(); kullanın.

2. Constructor yerine static factory metotlarını değerlendirin

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

Her zaman daha iyi değildir, ama bazı kullanım senaryolarında pek çok avantaj sunar:

Dezavantajları:

3. Çok sayıda constructor parametresiyle karşılaştığınızda bir builder düşünün

Static factory’ler ve constructor’lar ortak bir kısıtı paylaşır: Çok sayıda isteğe bağlı parametreye iyi ölçeklenmezler. Bu sorun, üzerinde çalıştığım bazı projeleri çok sert vurdu.

4. Singleton özelliğini private constructor veya enum tipiyle zorunlu kılın

Birden fazla örneğe ihtiyaç duymayan her şey yalnızca bir kez örneklenmelidir – ister @ApplicationScoped ile, ister başka bir singleton deseniyle.

5. Örneklenemezliği private constructor ile zorunlu kılın

Zaman zaman yalnızca static metotlar ve static alanlardan oluşan bir sınıf yazmak isteyeceksiniz. Bu tür sınıfların kötü bir ünü vardır, çünkü bazı insanlar nesneler cinsinden düşünmekten kaçınmak için bunları kötüye kullanır; yine de geçerli kullanım alanları vardır.

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

6. Gereksiz nesneler oluşturmaktan kaçının

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

Ya da:

// 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;
}

Çözüm: Bu program doğru sonucu verir, ama tek karakterlik bir yazım hatası yüzünden olması gerekenden çok daha yavaştır. sum değişkeni long yerine Long olarak tanımlanmıştır; bu da programın yaklaşık 2^31 gereksiz Long örneği oluşturduğu anlamına gelir (kabaca long i değeri Long sum’a her eklendiğinde bir tane).

7. Finalizer ve cleaner’lardan kaçının

Finalizer’lar öngörülemez, çoğu zaman tehlikeli ve genellikle gereksizdir. Kullanımları düzensiz davranışlara, düşük performansa ve taşınabilirlik sorunlarına yol açabilir.

8. try-finally yerine try-with-resources tercih edin

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

9. Comparable arayüzünü uygulamayı değerlendirin

Comparable’ı uyguladığınızda sınıfınızın, bu arayüze dayanan çok sayıdaki generic algoritma ve collection implementasyonuyla birlikte çalışabilmesini sağlarsınız.

10. Sınıfların ve üyelerin erişilebilirliğini en aza indirin

İyi tasarlanmış bir bileşeni kötü tasarlanmış olandan ayıran en önemli tek etken, bileşenin kendi iç verilerini ve diğer uygulama ayrıntılarını diğer bileşenlerden ne ölçüde gizlediğidir. İyi tasarlanmış bir bileşen tüm uygulama ayrıntılarını gizler ve API’sini implementasyonundan temiz bir şekilde ayırır. Bileşenler böylece yalnızca API’leri üzerinden iletişim kurar ve birbirlerinin iç işleyişinden habersizdir. Bilgi gizleme veya kapsülleme olarak bilinen bu kavram, yazılım tasarımının temel ilkelerinden biridir.

11. Public sınıflarda public alanlar yerine erişim metotları kullanın

Zaman zaman, örnek alanlarını gruplamaktan başka hiçbir amaca hizmet etmeyen dejenere sınıflar yazmak isteyebilirsiniz:

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

Bu tür sınıfların veri alanlarına doğrudan erişildiği için kapsüllemenin faydalarını sunmazlar. API’yi değiştirmeden temsili değiştiremez, değişmezleri (invariant) zorunlu kılamaz ve bir alana erişildiğinde ek bir işlem yapamazsınız. Katı nesne yönelimli programcılar bu tür sınıfları lanetli sayar ve her zaman private alanlara ve public erişim metotlarına (getter’lar), değiştirilebilir sınıflar için de değiştirici metotlara (setter’lar) sahip sınıflarla değiştirilmeleri gerektiğini düşünür:

// 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; }
}

Public sınıflar söz konusu olduğunda katı görüşlüler kesinlikle haklıdır: Bir sınıf kendi paketinin dışından erişilebiliyorsa, sınıfın iç temsilini değiştirme esnekliğini korumak için erişim metotları sağlayın. Public bir sınıf veri alanlarını dışarı açarsa, istemci kodu her yere dağılmış olabileceğinden temsilini değiştirme umudu tamamen ortadan kalkar.

Ancak bir sınıf package-private ise ya da private bir iç içe (nested) sınıfsa, veri alanlarını dışarı açmakta – sınıfın sunduğu soyutlamayı yeterince iyi tarif ettikleri sürece – özünde yanlış bir şey yoktur.

12. Değiştirilebilirliği en aza indirin

Immutable (değiştirilemez) bir sınıf, örnekleri değiştirilemeyen bir sınıftır. Her örneğin içerdiği tüm bilgiler nesnenin ömrü boyunca sabittir, dolayısıyla hiçbir değişiklik gözlemlenemez. Java platform kütüphaneleri; String, kutulanmış (boxed) primitif sınıflar, BigInteger ve BigDecimal dahil pek çok immutable sınıf içerir. Bunun birçok iyi nedeni vardır: Immutable sınıfları tasarlamak, uygulamak ve kullanmak, değiştirilebilir sınıflara göre daha kolaydır. Hataya daha az açık ve daha güvenlidirler. Bir sınıfı immutable yapmak için şu beş kurala uyun:

  1. Nesnenin state’ini değiştiren metotlar sunmayın (mutator olarak bilinirler).
  2. Sınıfın genişletilemeyeceğinden emin olun. Bu, dikkatsiz veya kötü niyetli alt sınıfların, nesnenin state’i değişmiş gibi davranarak sınıfın immutable davranışını bozmasını önler. Alt sınıf oluşturmanın önüne geçmek genellikle sınıfı örneğin final yaparak sağlanır.
  3. Tüm alanları final yapın. Bu, niyetinizi sistem tarafından zorunlu kılınan bir biçimde açıkça ifade eder. Ayrıca, yeni oluşturulmuş bir örneğe ait bir referans senkronizasyon olmadan bir thread’den diğerine aktarılırsa, bellek modelinde açıklandığı gibi doğru davranışı güvence altına almak için de gereklidir.
  4. Tüm alanları private yapın. Bu, istemcilerin alanların referans verdiği değiştirilebilir nesnelere erişip bunları doğrudan değiştirmesini önler. Immutable sınıfların primitif değerler ya da immutable nesnelere referanslar içeren public final alanlara sahip olması teknik olarak mümkün olsa da önerilmez, çünkü sonraki bir sürümde iç temsili değiştirmeyi imkânsız kılar.
  5. Değiştirilebilir bileşenlere münhasır erişim sağlayın. Sınıfınızın değiştirilebilir nesnelere referans veren alanları varsa, sınıfın istemcilerinin bu nesnelere referans elde edemeyeceğinden emin olun. Böyle bir alanı asla istemcinin verdiği bir nesne referansıyla başlatmayın ya da bir erişim metodundan döndürmeyin. Constructor’larda, erişim metotlarında ve readObject metotlarında savunmacı kopyalar (defensive copy) oluşturun.

13. Kalıtım yerine kompozisyonu tercih edin

Kalıtım, kodun yeniden kullanımını sağlamanın güçlü bir yoludur, ama iş için her zaman en iyi araç değildir. Uygunsuz kullanıldığında kırılgan yazılımlara yol açar. Alt sınıf ve üst sınıf implementasyonlarının aynı programcıların kontrolünde olduğu bir paket içinde kalıtım kullanmak güvenlidir. Özellikle genişletilmek üzere tasarlanmış ve belgelenmiş sınıfları genişletirken kalıtım kullanmak da güvenlidir. Ancak sıradan somut sınıflardan paket sınırları ötesinde kalıtım almak tehlikelidir.

Metot çağrısının aksine, kalıtım kapsüllemeyi ihlal eder. Yalnızca yeni metotlar ekleyip mevcut metotları override etmekten kaçınırsanız bir sınıfı genişletmenin güvenli olduğunu düşünebilirsiniz. Bu tür bir genişletme çok daha güvenli olsa da risksiz değildir. Üst sınıf sonraki bir sürümde yeni bir metot kazanırsa ve siz talihsizlik eseri alt sınıfa aynı imzaya ama farklı dönüş tipine sahip bir metot vermişseniz, alt sınıfınız artık derlenmez. Alt sınıfa, yeni üst sınıf metoduyla aynı imzaya ve dönüş tipine sahip bir metot vermişseniz, artık onu override ediyorsunuz demektir; dolayısıyla daha önce anlatılan sorunlarla karşı karşıyasınız. Üstelik metodunuzun yeni üst sınıf metodunun sözleşmesini yerine getirmesi şüphelidir, çünkü siz alt sınıf metodunu yazdığınızda bu sözleşme henüz yazılmamıştı.

Neyse ki yukarıda anlatılan tüm sorunlardan kaçınmanın bir yolu var. Mevcut bir sınıfı genişletmek yerine, yeni sınıfınıza mevcut sınıfın bir örneğine referans veren private bir alan ekleyin. Bu tasarıma kompozisyon denir, çünkü mevcut sınıf yeni sınıfın bir bileşeni hâline gelir. Yeni sınıftaki her örnek metodu, içerilen mevcut sınıf örneği üzerinde karşılık gelen metodu çağırır ve sonuçları döndürür. Buna yönlendirme (forwarding), yeni sınıftaki metotlara da yönlendirme metotları denir. Ortaya çıkan sınıf kaya gibi sağlam olur ve mevcut sınıfın uygulama ayrıntılarına hiçbir bağımlılığı bulunmaz. Mevcut sınıfa yeni metotlar eklemek bile yeni sınıfı etkilemez.

14. Soyut sınıflar yerine arayüzleri tercih edin

Java’da birden fazla implementasyona izin veren bir tip tanımlamak için iki mekanizma vardır: arayüzler ve soyut sınıflar. Java 8 ile arayüzlere default metotların gelmesinden bu yana her iki mekanizma da bazı örnek metotları için implementasyon sağlamanıza olanak tanır.

Önemli bir fark şudur: Soyut bir sınıfın tanımladığı tipi uygulamak için bir sınıfın o soyut sınıfın alt sınıfı olması gerekir. Java yalnızca tekli kalıtıma izin verdiğinden, soyut sınıflara getirilen bu kısıtlama, onların tip tanımı olarak kullanımını ciddi biçimde sınırlar. Gerekli tüm metotları tanımlayan ve genel sözleşmeye uyan her sınıf, sınıf hiyerarşisinde nerede bulunduğundan bağımsız olarak bir arayüzü uygulayabilir. Mevcut sınıflar, yeni bir arayüzü uygulayacak şekilde kolayca güncellenebilir.

15. Generic’ler: Raw type kullanmayın

Hatayı ancak çalışma zamanında, gerçekleşmesinden çok sonra ve hatayı içeren koddan uzak olabilecek bir kodda fark edersiniz. ClassCastException’ı gördüğünüzde, madeni parayı pul koleksiyonuna koyan metot çağrısını bulmak için kod tabanını taramanız gerekir. Derleyici size yardımcı olamaz.

16. Generic’ler: Unchecked uyarılarını ortadan kaldırın

Generic’lerle programlama yaptığınızda çok sayıda derleyici uyarısı görürsünüz:

Generic’lerle deneyiminiz arttıkça daha az uyarı alırsınız, ama yeni yazılmış kodun tertemiz derlenmesini beklemeyin. Birçok unchecked uyarısını gidermek kolaydır. Örneğin, yanlışlıkla şu tanımı yazdığınızı varsayalım:

Set<Lark> exaltation = new HashSet();

Bunu şöyle değiştirin:

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

Bazı uyarıları gidermek çok daha zor olacaktır. Biraz düşünmeyi gerektiren uyarılar aldığınızda pes etmeyin! Giderebildiğiniz her unchecked uyarısını giderin.

Tüm uyarıları giderirseniz, kodunuzun typesafe olduğundan emin olursunuz; bu da çok iyi bir şeydir. Çalışma zamanında ClassCastException almayacağınız anlamına gelir ve programınızın istediğiniz gibi davranacağına olan güveninizi artırır. Bir uyarıyı gideremiyor, ama uyarıya yol açan kodun typesafe olduğunu kanıtlayabiliyorsanız, o zaman (ve yalnızca o zaman) uyarıyı bir @SuppressWarnings("unchecked") anotasyonuyla bastırın. Kodun typesafe olduğunu önce kanıtlamadan uyarıları bastırırsanız, kendinize sahte bir güvenlik duygusu vermiş olursunuz. Kod hiçbir uyarı vermeden derlenebilir, ama çalışma zamanında yine de ClassCastException fırlatabilir.

@SuppressWarnings("unchecked") anotasyonunu her kullandığınızda, bunun neden güvenli olduğunu açıklayan bir yorum ekleyin.

17. Generic’ler: Ne zaman anlamlı olduğu

Eyvah – generic’lere fena hâlde ihtiyaç duyan bir sınıf:

Generic’lere ihtiyaç duyan bir sınıf örneği

Böyle bir şey en geç merge request aşamasında yakalanmalı!

18. Generic’ler: Nasıl yapılmamalı

Kusura bakmayın, söze gerek yok. Lütfen bunu ASLA merge etmeyin:

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> {
    // ...
}

Basit bir sınıf tanımını anlamak asla 5 dakika sürmemeli.

19. Diziler yerine listeleri tercih edin

Diziler, generic tiplerden iki önemli açıdan ayrılır. Birincisi, diziler kovaryanttır. Kulağa korkutucu gelen bu kelime basitçe şu anlama gelir: Sub, Super’ın bir alt tipiyse, Sub[] dizi tipi de Super[] dizi tipinin bir alt tipidir. Generic’ler ise invaryanttır: Birbirinden farklı herhangi iki tip Type1 ve Type2 için List<Type1>, List<Type2> tipinin ne alt tipi ne de üst tipidir.

20. Anonim sınıflar yerine lambda’ları tercih edin

// 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());
    }
});

Bunun yerine şunu yapın:

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

21. Yerel değişkenlerin kapsamını en aza indirin

Bir yerel değişkenin kapsamını en aza indirmenin en etkili tekniği, onu ilk kullanıldığı yerde tanımlamaktır...

22. Geleneksel for döngüleri yerine for-each döngülerini tercih edin

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

23. Kütüphaneleri tanıyın ve kullanın

Sıfır ile bir üst sınır arasında rastgele tam sayılar üretmek istediğinizi varsayalım. Bu yaygın görevle karşılaşan birçok programcı, aşağı yukarı şuna benzeyen küçük bir metot yazar:

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

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

Bu metot iyi görünebilir, ama üç kusuru vardır. Birincisi, n ikinin küçük bir kuvvetiyse, rastgele sayı dizisi oldukça kısa bir periyottan sonra kendini tekrar eder. İkinci kusur, n ikinin bir kuvveti değilse bazı sayıların ortalamada diğerlerinden daha sık döndürülmesidir. n büyükse bu etki oldukça belirgin olabilir.

random metodunun üçüncü kusuru, nadir durumlarda feci şekilde başarısız olup belirtilen aralığın dışında bir sayı döndürebilmesidir. Bunun nedeni, metodun rnd.nextInt() tarafından döndürülen değeri Math.abs çağırarak negatif olmayan bir int değerine eşlemeye çalışmasıdır. nextInt() Integer.MIN_VALUE döndürürse, Math.abs de Integer.MIN_VALUE döndürür ve kalan operatörü (%), n ikinin bir kuvveti değilse negatif bir sayı döndürür. Bu neredeyse kesinlikle programınızın çökmesine yol açar ve hatayı yeniden üretmek zor olabilir.

24. Kesin sonuç gerekiyorsa float ve double kullanmaktan kaçının

Parasal tutarlar için BigDecimal kullanın. float ve double yuvarlama hatalarına yol açar.

Örneğin cebinizde 1,03 $ olduğunu ve 42 ¢ harcadığınızı varsayalım. Geriye ne kadar paranız kalır? İşte bu soruyu yanıtlamaya çalışan saf bir program parçası:

System.out.println(1.03 - 0.42);

Ne yazık ki ekrana 0.6100000000000001 yazdırır. Bu tek örnek de değil. Cebinizde bir dolar olduğunu ve tanesi on sent olan dokuz rondela aldığınızı varsayalım. Ne kadar para üstü alırsınız?

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

Bu program parçasına göre $0.09999999999999998 alırsınız.

Sorunun, sonuçları yazdırmadan önce yuvarlayarak çözülebileceğini düşünebilirsiniz, ama ne yazık ki bu her zaman işe yaramaz. Örneğin cebinizde bir dolar olduğunu ve rafta fiyatları 10 ¢, 20 ¢, 30 ¢ diye bir dolara kadar giden bir sıra nefis şekerleme gördüğünüzü varsayalım. 10 ¢ olandan başlayarak her şekerlemeden birer tane alıyorsunuz, ta ki raftaki bir sonrakine paranız yetmeyene kadar.

25. Kutulanmış primitifler yerine primitif tipleri tercih edin

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

26. Nesnelere arayüzleri üzerinden referans verin

İyi örnek:

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

Kötü örnek:

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

27. Reflection yerine arayüzleri tercih edin

Aksi hâlde:

28. Genel kabul görmüş isimlendirme kurallarına uyun

Java platformunun köklü bir isimlendirme kuralları seti vardır; bunların birçoğu The Java Language Specification’da [JLS, 6.1] yer alır.

29. Exception’ları yalnızca istisnai durumlar için kullanın

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

Bu kod ne yapıyor? İncelemekle hiç de anlaşılmıyor ve bu, onu kullanmamak için yeterli bir neden. Meğer bir dizinin elemanları üzerinde döngü kurmak için korkunç derecede kötü düşünülmüş bir deyimmiş. Sonsuz döngü, dizinin sınırları dışındaki ilk elemana erişmeye çalıştığında bir ArrayIndexOutOfBoundsException fırlatıp yakalayarak ve görmezden gelerek sona eriyor. Bir dizi üzerinde döngü kurmanın, her Java programcısının anında tanıyacağı standart deyimine denk olması amaçlanmış:

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

Exception’lar, adlarından da anlaşılacağı gibi, yalnızca istisnai durumlar için kullanılmalıdır; asla olağan kontrol akışı için kullanılmamalıdır.

30. Kurtarılabilir durumlar için checked exception’ları, programlama hataları için runtime exception’ları kullanın

Çağıranın makul olarak toparlanabileceği durumlar için checked exception kullanın. API tasarımcısı, kullanıcıyı bir checked exception ile karşı karşıya bırakarak o durumdan toparlanma yükümlülüğü getirir. Kullanıcı exception’ı yakalayıp görmezden gelerek bu yükümlülüğü yok sayabilir, ama bu genellikle kötü bir fikirdir.

Programlama hatalarını belirtmek için runtime exception kullanın. Runtime exception’ların büyük çoğunluğu ön koşul ihlallerini belirtir. Ön koşul ihlali, basitçe bir API’nin istemcisinin API spesifikasyonunun belirlediği sözleşmeye uymamasıdır. Örneğin dizi erişimi sözleşmesi, dizi indeksinin sıfır ile dizi uzunluğunun bir eksiği arasında (sınırlar dahil) olması gerektiğini belirtir. ArrayIndexOutOfBoundsException, bu ön koşulun ihlal edildiğini gösterir.

31. Checked exception’ların gereksiz kullanımından kaçının

Özetle: Ölçülü kullanıldığında checked exception’lar programların güvenilirliğini artırabilir; aşırı kullanıldığında ise API’lerin kullanımını eziyete çevirir.

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. Standart exception’ları kullanmayı tercih edin

Uzman programcıları daha az deneyimli olanlardan ayıran bir özellik, uzmanların yüksek düzeyde kod yeniden kullanımı için çabalaması ve genellikle bunu başarmasıdır.

Exception’lar da, kodun yeniden kullanımının iyi bir şey olduğu kuralının istisnası değildir. Java kütüphaneleri, çoğu API’nin exception fırlatma ihtiyaçlarının büyük kısmını karşılayan bir exception seti sunar. Standart exception’ları yeniden kullanmanın birçok faydası vardır. Bunların başında, API’nizin programcıların zaten aşina olduğu yerleşik kurallarla örtüşmesi sayesinde öğrenilmesinin ve kullanılmasının kolaylaşması gelir. Hemen ardından, API’nizi kullanan programların alışılmadık exception’larla dolu olmadığı için daha kolay okunması gelir. Son olarak (ve en önemsiz olarak), daha az exception sınıfı daha küçük bir bellek ayak izi ve sınıfları yüklemeye harcanan daha az zaman demektir.

En sık yeniden kullanılan exception tipi IllegalArgumentException’dır. Bu, genellikle çağıran, değeri uygunsuz bir argüman verdiğinde fırlatılacak exception’dır. Örneğin çağıran, bir işlemin kaç kez tekrarlanacağını belirten bir parametreye negatif bir sayı verirse fırlatılacak exception budur. Sık yeniden kullanılan bir diğer exception IllegalStateException’dır. Bu, genellikle çağrı, çağrıyı alan nesnenin state’i nedeniyle geçersizse fırlatılacak exception’dır. Örneğin çağıran, bir nesneyi düzgün şekilde başlatılmadan önce kullanmaya çalışırsa fırlatılacak exception budur.

Tartışmaya açık olsa da her hatalı metot çağrısı özünde geçersiz bir argümana veya state’e indirgenir; ancak belirli türdeki geçersiz argümanlar ve state’ler için standart olarak başka exception’lar kullanılır. Çağıran, null değerlerin yasak olduğu bir parametreye null verirse, teamül olarak IllegalArgumentException yerine NullPointerException fırlatılır. Benzer şekilde, çağıran bir dizideki indeksi temsil eden bir parametreye aralık dışında bir değer verirse, IllegalArgumentException yerine IndexOutOfBoundsException fırlatılmalıdır.

Exception, RuntimeException, Throwable veya Error sınıflarını doğrudan yeniden kullanmayın. Bu sınıflara soyutmuş gibi davranın. Gerçi checked bir exception’ı runtime exception’a dönüştürmek istiyorsanız ve ne yaptığınızı gerçekten biliyorsanız RuntimeException kullanılabilir.

33. Exception’ları görmezden gelmeyin

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

Boş bir catch bloğu exception’ların amacını boşa çıkarır; bu amaç, sizi istisnai durumları ele almaya zorlamaktır. Bir exception’ı görmezden gelmek, bir yangın alarmını görmezden gelmeye – ve başka kimse gerçek bir yangın olup olmadığını göremesin diye alarmı kapatmaya – benzer.