← Kursa Dön
📄 Text · 12 min

Exception Hiyerarşisi

Java'daki exception'lar rastgele sınıflar değil. Hepsi düzenli bir soy ağacı (hiyerarşi) içinde yer alır. Bu ağacı anlamak, hangi exception'ı ne zaman yakalayacağını, hangisini yakalamaman gerektiğini bilmen için şart.

Büyük Resim: Throwable Ailesi

Java'da fırlatılabilir (throwable) her şeyin atası Throwable sınıfıdır. Bu sınıfın iki çocuğu var:

                  Throwable
                 /         \
              Error       Exception
               |          /       \
          OutOfMemory  IOException  RuntimeException
          StackOverflow  SQLException   |         \
                        ParseException NullPointer ArithmeticException
                                       ClassCast   IndexOutOfBounds

Bunu bir hastane gibi düşün. Throwable hastanenin kendisi. İki ana bölüm var:

  • Error = Yoğun bakım. Müdahale edemezsin, çok ciddi durumlar.

  • Exception = Normal poliklinik. Teşhis koyar, tedavi edersin (catch).

Ve Exception'ın içinde özel bir bölüm daha var:

  • RuntimeException = Acil servis. Kendi hatandan kaynaklanan, genellikle önlenebilir sorunlar.

Throwable Sınıfı

Her şeyin başı. Throwable, throw ile fırlatılabilen ve catch ile yakalanabilen tek sınıf ailesidir.

// Throwable'ın temel method'ları
try {
    throw new Exception("Bir şeyler ters gitti");
} catch (Throwable t) {
    System.out.println(t.getMessage());      // "Bir şeyler ters gitti"
    System.out.println(t.getClass());        // class java.lang.Exception
    t.printStackTrace();                      // Tam stack trace
    
    StackTraceElement[] stack = t.getStackTrace();
    for (StackTraceElement frame : stack) {
        System.out.println(frame.getFileName() + ":" + frame.getLineNumber());
    }
}

getMessage(), printStackTrace(), getStackTrace() — bu üç method debugging'in temel silahları. Hepsini Throwable'dan miras alır tüm exception'lar.

⚠️ Ama dikkat: catch (Throwable t) yazmak neredeyse her zaman yanlıştır. Error'ları da yakalarsın ki onlara müdahale edemezsin. Birazdan nedenini göreceksin.

Error: Dokunma, Kurtarılamaz

Error sınıfı ve alt sınıfları, JVM seviyesinde ciddi sorunları temsil eder. Bunlar senin kodunun çözemeyeceği problemler.

// OutOfMemoryError — JVM'in belleği tükendi
public class MemoryBomb {
    public static void main(String[] args) {
        List<byte[]> list = new ArrayList<>();
        while (true) {
            list.add(new byte[1024 * 1024]); // Her seferinde 1MB ekle
            // Sonunda: java.lang.OutOfMemoryError: Java heap space
        }
    }
}
// StackOverflowError — Sonsuz recursion
public class Infinite {
    public static void forever() {
        forever(); // Kendini çağırıyor, stack doluyor
    }
    
    public static void main(String[] args) {
        forever(); // StackOverflowError!
    }
}

Yaygın Error türleri:

ErrorNe Zaman Olur
OutOfMemoryErrorJVM'in heap alanı tükendi
StackOverflowErrorSonsuz/çok derin recursion
VirtualMachineErrorJVM'in kendisi bozuldu
NoClassDefFoundErrorCompile'da olan sınıf runtime'da bulunamadı
ExceptionInInitializerErrorStatic initializer'da hata

Bu hataları yakalama. Yakalasan bile anlamlı bir şey yapamazsın. JVM'in belleği bittiyse, senin catch bloğun da çalışamaz.

// YAPMA — anlamsız
try {
    riskyOperation();
} catch (OutOfMemoryError e) {
    System.out.println("Bellek bitti"); // Bu satır bile çalışmayabilir!
}

// İstisna: Belki sadece loglama için, en üst seviyede
try {
    startApplication();
} catch (Error e) {
    logger.fatal("Kritik hata, uygulama kapanıyor", e);
    System.exit(1);
}

Exception: İşte Seninle İlgili Olan Kısım

Exception sınıfı ve alt sınıfları, programının makul şekilde kurtarılabileceği sorunları temsil eder. İki büyük kategoriye ayrılır:

  1. Checked ExceptionsRuntimeException dışındaki tüm Exception alt sınıfları

  2. Unchecked ExceptionsRuntimeException ve alt sınıfları

Bu ayrım Java'nın en tartışmalı tasarım kararlarından biri. Ama anlamanız şart.

Checked Exceptions: "Bunu Hallettim" Garantisi

Checked exception'lar, compiler'ın seni zorla handle etmeye zorladığı exception'lardır. Ya try-catch ile yakala, ya da throws ile yukarı bildir.

// Bu KOD DERLENMEZ — IOException checked'tır
public void readFile() {
    FileReader fr = new FileReader("test.txt"); // Compile error!
}

// Seçenek 1: try-catch ile yakala
public void readFile() {
    try {
        FileReader fr = new FileReader("test.txt");
    } catch (FileNotFoundException e) {
        System.out.println("Dosya bulunamadı");
    }
}

// Seçenek 2: throws ile bildir
public void readFile() throws FileNotFoundException {
    FileReader fr = new FileReader("test.txt");
}

Yaygın checked exception'lar:

ExceptionNe Zaman
IOExceptionDosya/ağ I/O hataları
FileNotFoundExceptionDosya bulunamadı
SQLExceptionVeritabanı hataları
ParseExceptionTarih/format parse hataları
ClassNotFoundExceptionSınıf yüklenemedi
CloneNotSupportedExceptionclone() desteklenmiyor
InterruptedExceptionThread kesintisi

Checked exception'ların mantığı şu: "Bu hatalar gerçekçi senaryolarda olabilir. Dosya olmayabilir, ağ kopabilir. Compiler seni bunu düşünmeye zorluyor."

💡 İpucu: Checked exception gördüğünde şöyle düşün: "Java bana diyor ki — hey, bu olabilir, planın ne?"

Unchecked Exceptions (RuntimeException): Senin Hatan

RuntimeException ve alt sınıfları programcı hatalarını temsil eder. Compiler seni zorlamaz — ama olduğunda program çöker.

// NullPointerException — null referans kullanımı
String name = null;
System.out.println(name.length()); // BOOM!

// ArrayIndexOutOfBoundsException — geçersiz index
int[] arr = {1, 2, 3};
System.out.println(arr[5]); // BOOM!

// ArithmeticException — sıfıra bölme
int result = 10 / 0; // BOOM!

// ClassCastException — yanlış tür dönüşümü
Object obj = "hello";
Integer num = (Integer) obj; // BOOM!

Neden compiler zorlamıyor? Çünkü bu hatalar önlenebilir. Null kontrolü yap, index kontrolü yap, sıfır kontrolü yap. Her method çağrısını try-catch'e sarmak kodu okunamaz hale getirirdi.

Yaygın unchecked exception'lar:

ExceptionSebep
NullPointerExceptionnull referansta method/field erişimi
ArrayIndexOutOfBoundsExceptionDizinin sınırları dışı
StringIndexOutOfBoundsExceptionString'in sınırları dışı
ArithmeticExceptionSıfıra bölme (integer)
ClassCastExceptionUyumsuz cast
IllegalArgumentExceptionGeçersiz method argümanı
IllegalStateExceptionNesne yanlış durumda
NumberFormatExceptionString → sayı çevirme hatası
UnsupportedOperationExceptionDesteklenmeyen operasyon
ConcurrentModificationExceptionİterasyon sırasında koleksiyon değişimi

NullPointerException Detaylı

Java programcılarının en çok karşılaştığı exception. Tony Hoare null referansı "milyar dolarlık hata" olarak adlandırdı. Java 14+ sürümlerinde artık daha açıklayıcı mesajlar veriyor:

// Java 14+ — Helpful NullPointerExceptions
String city = user.getAddress().getCity().toUpperCase();
// Eski: NullPointerException
// Java 14+: Cannot invoke "String.toUpperCase()" because the return value of 
//           "Address.getCity()" is null
// Nasıl önlenir?
// 1. Null kontrolü
if (user != null && user.getAddress() != null) {
    String city = user.getAddress().getCity();
}

// 2. Optional kullanımı (Java 8+)
Optional.ofNullable(user)
    .map(User::getAddress)
    .map(Address::getCity)
    .map(String::toUpperCase)
    .ifPresent(System.out::println);

// 3. Objects.requireNonNull — fail-fast
public void setName(String name) {
    this.name = Objects.requireNonNull(name, "İsim null olamaz");
}

ClassCastException Detaylı

Yanlış tür dönüşümü yapmaya çalıştığında fırlar:

Object obj = "Merhaba";
Integer num = (Integer) obj; // ClassCastException!

// Nasıl önlenir? instanceof kontrolü
if (obj instanceof Integer) {
    Integer num = (Integer) obj;
}

// Java 16+ — Pattern Matching
if (obj instanceof String s) {
    System.out.println(s.toUpperCase()); // Otomatik cast
}

ArrayIndexOutOfBoundsException Detaylı

Dizinin sınırları dışına çıktığında:

int[] numbers = {10, 20, 30};
System.out.println(numbers[3]);  // HATA! Index: 0, 1, 2
System.out.println(numbers[-1]); // HATA! Negatif index yok

// Nasıl önlenir?
if (index >= 0 && index < numbers.length) {
    System.out.println(numbers[index]);
}

// For-each ile index sorunu olmaz
for (int num : numbers) {
    System.out.println(num);
}

Checked vs Unchecked: Karar Tablosu

Ne zaman hangisini kullanırsın? İşte pratik bir rehber:

Soru: Bu hatayı çağıran kod MAKUL bir şekilde kurtarabilir mi?
  ├─ EVET → Checked Exception
  │   Örnek: Dosya bulunamadı → kullanıcıya sor, başka dosya dene
  │   Örnek: Ağ hatası → tekrar dene, cache'den oku
  │
  └─ HAYIR (programcı hatası) → Unchecked Exception
      Örnek: null geçildi → bu bir bug, fix et
      Örnek: Negatif yaş → validasyon ekle

Pratikte çoğu modern framework (Spring gibi) unchecked exception'ları tercih eder. Checked exception'lar kodu kalabalıklaştırabilir. Ama JDK'nın I/O, network, database API'leri hâlâ checked kullanır — bunlarla çalışırken mecburen handle edeceksin.

Kendi Exception Hiyerarşini Oluşturma

Büyük projelerde genellikle proje bazlı bir exception hiyerarşisi kurarsın:

// Base exception — tüm proje exception'larının atası
public class AppException extends RuntimeException {
    private final String errorCode;
    
    public AppException(String message, String errorCode) {
        super(message);
        this.errorCode = errorCode;
    }
    
    public String getErrorCode() { return errorCode; }
}

// Spesifik exception'lar
public class UserNotFoundException extends AppException {
    public UserNotFoundException(Long userId) {
        super("Kullanıcı bulunamadı: " + userId, "USER_NOT_FOUND");
    }
}

public class InsufficientBalanceException extends AppException {
    public InsufficientBalanceException(double required, double available) {
        super("Yetersiz bakiye. Gereken: " + required + ", Mevcut: " + available,
              "INSUFFICIENT_BALANCE");
    }
}

exception instanceof ve Polimorfizm

Exception hiyerarşisi polimorfik davranır. Üst sınıf catch'i, alt sınıf exception'ları da yakalar:

try {
    // IOException fırlatabilir
    readFile("data.txt");
} catch (FileNotFoundException e) {
    // Sadece FileNotFoundException
    System.out.println("Dosya yok");
} catch (IOException e) {
    // FileNotFoundException HARİÇ tüm IOException'lar
    System.out.println("IO hatası");
} catch (Exception e) {
    // IOException HARİÇ tüm Exception'lar
    System.out.println("Genel hata");
}

Java ilk eşleşen catch'i çalıştırır. Bu yüzden sıralama özelden genele olmalı.

Özet

  • Throwable her şeyin atası — iki kolu var: Error (dokunma) ve Exception (handle et)

  • Error JVM seviyesinde kurtarılamaz sorunlar — OutOfMemoryError, StackOverflowError — yakalamaya çalışma

  • Checked Exception compiler tarafından kontrol edilir — ya try-catch ya throws ile handle et (IOException, SQLException)

  • Unchecked Exception (RuntimeException alt sınıfları) programcı hataları — compiler zorlamaz ama çalışma zamanında patlar (NullPointerException, ClassCastException)

  • NullPointerException en yaygın hata — null kontrolü, Optional, Objects.requireNonNull ile önle

  • Hiyerarşiyi bilmek, catch bloklarını doğru sırayla (özelden genele) yazmak için şart