SEN1002 · 9. hafta · Exception Handling

İstisna yönetimi: try, catch, finally, throw, throws

Hata olunca kontrol nereye gider, finally ne zaman çalışır, checked ve unchecked farkı, kendi istisna sınıfını yazmak. Çıktı izleme sorularının en sevileni.

exceptiontrycatchfinallythrowthrowschecked exceptionunchecked exceptionRuntimeExceptionErrorstack unwindingmulti-catchcustom exceptiongetMessageexception chaining

Programın her şey yolundayken ne yaptığını yazmak kolay. İstisna yönetimi (exception handling), işler ters gittiğinde ne olacağını düzenler: kullanıcı sayı yerine harf girer, dosya yoktur, dizi sınırı aşılır. Sınavda bu konu neredeyse her zaman bir “What is printed?” izleme sorusu olarak gelir: hangi satırlar atlanır, hangi catch çalışır, finally nerede araya girer. Yanında checked istisnalarla ilgili derleme hataları ve “kendi istisna sınıfını yaz” sorusu.

Altın kurallar:

  1. İstisna fırladığı an try bloğunun kalanı atlanır. Kontrol eşleşen catch’e gider.
  2. Catch’ler yukarıdan aşağı denenir, ilk uyan çalışır. Uyan, istisnanın kendi tipi ya da üst sınıfıdır.
  3. finally her zaman çalışır: istisna olsa da olmasa da, return olsa da.
  4. Yakalanmayan istisna metodu anında bitirir ve çağırana geçer (stack unwinding).
  5. Checked istisna ya yakalanır ya throws ile bildirilir. Unchecked (RuntimeException) için zorunluluk yok.

try, catch, finally

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[1]);
    System.out.println(arr[3]);        // istisna burada
    System.out.println("not printed"); // atlanır
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Caught: " + e.getMessage());
} finally {
    System.out.println("finally");
}
System.out.println("after");
2
Caught: Index 3 out of bounds for length 3
finally
after

Kurallar:

  • try tek başına duramaz: en az bir catch ya da bir finally gerekir. Yoksa 'try' without 'catch', 'finally' or resource declarations.
  • Bir try’ın birden fazla catch’i olabilir; sadece biri çalışır.
  • finally isteğe bağlıdır; varsa en sonda bir kez çalışır. Kaynakları kapatmak (dosya, bağlantı) için kullanılır.

Birden fazla catch ve sıra

static void trace(int k) {
    try {
        System.out.print("A");
        if (k == 1) throw new IllegalStateException();
        if (k == 2) throw new IllegalArgumentException();
        System.out.print("B");
    } catch (IllegalStateException e) {
        System.out.print("C");
    } catch (RuntimeException e) {       // üst sınıf: her RuntimeException'ı yakalar
        System.out.print("D");
    } finally {
        System.out.print("E");
    }
    System.out.println("F");
}
trace(0);
trace(1);
trace(2);
ABEF
ACEF
ADEF

Genel catch’i (Exception, RuntimeException) özel olanlardan önce yazarsan alttaki catch’e hiçbir zaman sıra gelmez ve derleyici bunu hata sayar: exception NumberFormatException has already been caught.

Multi-catch: aynı işi yapan catch’leri birleştirmek için catch (NumberFormatException | ArithmeticException e) yazılır. Listede birbirinin alt sınıfı olan tipler olamaz.

static void parse(String s) {
    try {
        int n = Integer.parseInt(s);
        System.out.println(100 / n);
    } catch (NumberFormatException | ArithmeticException e) {
        System.out.println("bad input: " + e.getClass().getSimpleName());
    }
}
parse("4"); parse("x"); parse("0");
25
bad input: NumberFormatException
bad input: ArithmeticException

finally ve return

static int f() {
    try { return 1; }
    finally { System.out.println("finally in f"); }
}
static int h() {
    int x = 10;
    try { return x; }          // dönüş değeri 10 olarak saklandı
    finally { x = 20; }        // saklanan değeri etkilemez
}
System.out.println(f());
System.out.println(h());
finally in f
1
10

TUZAK · finally içinde return. finally’ye return 2; yazarsan try’dan fırlayan istisna bile yutulur ve metot 2 döner (denemede throw new RuntimeException olan try + return 2 olan finally, sessizce 2 döndürdü). Derleyici bunu reddetmez (sadece -Xlint açıkken uyarı verir); asla böyle yazma ama sınavda görürsen cevap finally’deki değer.

İstisna hiyerarşisi: checked ve unchecked

Throwable
 ├─ Error                       (JVM sorunları: OutOfMemoryError, StackOverflowError; yakalanmaz)
 └─ Exception                   (CHECKED: derleyici kontrol eder)
     ├─ IOException
     │    └─ FileNotFoundException
     ├─ (senin extends Exception sınıfların)
     └─ RuntimeException        (UNCHECKED: derleyici karışmaz)
          ├─ ArithmeticException
          ├─ NullPointerException
          ├─ ClassCastException
          ├─ IndexOutOfBoundsException
          │    ├─ ArrayIndexOutOfBoundsException
          │    └─ StringIndexOutOfBoundsException
          └─ IllegalArgumentException
               └─ NumberFormatException
CheckedUnchecked
Kimden türer?Exception (RuntimeException hariç)RuntimeException
Derleyici ne ister?Ya catch ya throwsHiçbir şey
Ne zaman olur?Programın kontrolü dışındaki durumlar: dosya yok, ağ koptuProgramlama hataları: null, sıfıra bölme, yanlış indeks
ÖrnekIOException, FileNotFoundExceptionArithmeticException, NullPointerException

Sık görülen unchecked istisnalar ve onları üreten tek satırlar:

String s = null; s.length();          // NullPointerException
Object o = "hi"; Integer i = (Integer) o;   // ClassCastException
System.out.println(10 % 0);           // ArithmeticException: / by zero
System.out.println(10.0 / 0);         // istisna YOK: Infinity
NullPointerException
ClassCastException
ArithmeticException: / by zero
Infinity

(İlk üç satırın çıktısı, her biri try/catch içinde çalıştırılıp yakalanan istisnanın adı basılarak alındı.)

throw ve throws

  • throw bir istisna fırlatır. Ardından bir nesne gelir: throw new IllegalArgumentException("amount must be positive");
  • throws metodun imzasında, bu metodun hangi checked istisnaları dışarı bırakabileceğini bildirir: void withdraw(double a) throws InsufficientFundsException.

Checked bir istisnayı fırlatan ya da fırlatabilen bir metodu çağırıyorsan iki seçeneğin var: try/catch ile yakala ya da kendi imzana throws ekleyip sorumluluğu yukarı devret. İkisini de yapmazsan: unreported exception X; must be caught or declared to be thrown.

Bir de ters yönde kural var: try bloğunda hiçbir şey bir checked istisnayı fırlatamıyorsa onu yakalayamazsın: exception IOException is never thrown in body of corresponding try statement.

Stack unwinding

Bir metotta istisna olur ve orada yakalanmazsa metot o anda biter. İstisna, metodu çağıran yere “fırlatılmış” gibi davranır. Yakalayan bir catch bulunana kadar çağrı yığını (call stack) böyle geri sarılır. Yol üstündeki her finally çalışır.

static void level3() {
    System.out.println("level3 start");
    int[] a = new int[2];
    a[5] = 1;                                   // istisna
    System.out.println("level3 end");
}
static void level2() {
    System.out.println("level2 start");
    try { level3(); }
    finally { System.out.println("level2 finally"); }
    System.out.println("level2 end");
}
static void level1() {
    System.out.println("level1 start");
    try { level2(); }
    catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("level1 caught: " + e.getMessage());
    }
    System.out.println("level1 end");
}
level1();
level1 start
level2 start
level3 start
level2 finally
level1 caught: Index 5 out of bounds for length 2
level1 end
main → level1 → level2 → level3        (çağrı yığını)
level3: a[5] → istisna, catch yok → level3 biter ("end" yok)
level2: catch yok, finally VAR → "level2 finally" → istisna devam ("end" yok)
level1: catch uyuyor → "level1 caught..." → normal akış → "level1 end"

İstisna main’e kadar çıkar ve orada da yakalanmazsa program biter ve JVM yığın izini (stack trace) basar. level3() doğrudan main’den çağrılınca:

level3 start
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 2
	at W09.level3(W09.java:31)
	at W09.main(W09.java:221)

Stack trace aşağıdan yukarı okunur: main, level3’ü çağırdı; istisna level3’ün 31. satırında oldu.

Kendi istisna sınıfın

İş kuralı ihlallerini (yetersiz bakiye, geçersiz not) anlamlı bir tiple bildirmek için kendi istisna sınıfını yazarsın. Checked olsun istiyorsan Exception’dan, unchecked olsun istiyorsan RuntimeException’dan türet. Mesajı super(message) ile ilet.

class InsufficientFundsException extends Exception {
    private final double shortfall;
    public InsufficientFundsException(String message, double shortfall) {
        super(message);
        this.shortfall = shortfall;
    }
    public double getShortfall() { return shortfall; }
}

class Account {
    private double balance;
    Account(double b) { balance = b; }
    void withdraw(double amount) throws InsufficientFundsException {
        if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
        if (amount > balance)
            throw new InsufficientFundsException("Not enough money", amount - balance);
        balance -= amount;
    }
    double getBalance() { return balance; }
}
Account acc = new Account(100);
try {
    acc.withdraw(30);
    acc.withdraw(100);
    System.out.println("not printed");
} catch (InsufficientFundsException e) {
    System.out.println(e.getMessage() + ", short by " + e.getShortfall());
}
System.out.println(acc.getBalance());
try {
    acc.withdraw(-5);
} catch (InsufficientFundsException e) {
    System.out.println("checked");
} catch (IllegalArgumentException e) {
    System.out.println("unchecked: " + e.getMessage());
}
Not enough money, short by 30.0
70.0
unchecked: amount must be positive

İstisna nesnesinin metotları

MetotNe verir?Örnek
getMessage()Sadece mesajbad value
toString()Sınıf adı + mesajjava.lang.IllegalArgumentException: bad value
printStackTrace()Yığın izini stderr’e basaryukarıdaki stack trace
getCause()Bu istisnaya yol açan istisnazincirleme

Bir istisnayı yakalayıp daha anlamlı bir başkasına sarmaya exception chaining denir: throw new IOException("load failed", e);. Sonra e.getCause().getMessage() asıl sebebi (data.txt) verir.

Sık yapılan hatalar

Yanlış düşünceDoğrusu
İstisnadan sonra try’ın kalanı da çalışırAtlanır, catch’e geçilir
Birden fazla catch çalışabilirSadece ilk uyan
catch (Exception e) en üsteAlttakiler erişilemez olur, derleme hatası
return varsa finally çalışmazÇalışır, return’den önce
finally’de değişkeni değiştirmek dönüşü değiştirirDönüş değeri önceden saklandı
NumberFormatException checkedRuntimeException ailesinden, unchecked
throws istisnayı fırlatırthrow fırlatır, throws bildirir
Checked istisna yakalanmadan çağrılabilircatch ya da throws şart
10.0 / 0 ArithmeticExceptiondouble’da Infinity; sadece int bölmede istisna
Yakalanan istisnadan sonra program biterCatch’ten sonra normal akış devam eder

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

What is printed?

try {
    int[] arr = {1, 2, 3};
    System.out.println(arr[1]);
    System.out.println(arr[3]);
    System.out.println("not printed");
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Caught: " + e.getMessage());
} finally {
    System.out.println("finally");
}
System.out.println("after");
Cevabı göster
2 / Caught: Index 3 out of bounds for length 3 / finally / after

arr[1] = 2 basılır. arr[3] istisna fırlatır: try'ın kalanı ATLANIR, eşleşen catch çalışır. finally her durumda çalışır. İstisna yakalandığı için program normal devam eder ve "after" basılır.

Soru 2orta

What is printed by trace(0); trace(1); trace(2);?

static void trace(int k) {
    try {
        System.out.print("A");
        if (k == 1) throw new IllegalStateException();
        if (k == 2) throw new IllegalArgumentException();
        System.out.print("B");
    } catch (IllegalStateException e) {
        System.out.print("C");
    } catch (RuntimeException e) {
        System.out.print("D");
    } finally {
        System.out.print("E");
    }
    System.out.println("F");
}
Cevabı göster
ABEF / ACEF / ADEF

k = 0: istisna yok, A B, catch'ler atlanır, finally E, sonra F. k = 1: A, IllegalStateException ilk catch'e uyar: C, E, F. k = 2: A, IllegalArgumentException ilk catch'e uymaz, ikinci catch RuntimeException onun üst sınıfı: D, E, F. Catch'ler yukarıdan aşağı denenir, ilk uyan çalışır.

Soru 3zor

What is printed by level1();?

static void level3() {
    System.out.println("level3 start");
    int[] a = new int[2];
    a[5] = 1;
    System.out.println("level3 end");
}
static void level2() {
    System.out.println("level2 start");
    try { level3(); }
    finally { System.out.println("level2 finally"); }
    System.out.println("level2 end");
}
static void level1() {
    System.out.println("level1 start");
    try { level2(); }
    catch (ArrayIndexOutOfBoundsException e) {
        System.out.println("level1 caught: " + e.getMessage());
    }
    System.out.println("level1 end");
}
Cevabı göster
level1 start / level2 start / level3 start / level2 finally / level1 caught: Index 5 out of bounds for length 2 / level1 end

Stack unwinding: level3'te istisna olur, orada catch yok, metot hemen biter ("level3 end" basılmaz). level2'de catch yok ama finally var: önce finally çalışır, sonra istisna yukarı devam eder ("level2 end" basılmaz). level1'de yakalanır, mesaj basılır, level1 normal devam eder.

Soru 4zor

What do f() and h() return, and what is printed?

static int f() {
    try { return 1; }
    finally { System.out.println("finally in f"); }
}
static int h() {
    int x = 10;
    try { return x; }
    finally { x = 20; }
}
// in main:
System.out.println(f());
System.out.println(h());
Cevabı göster
finally in f / 1 / 10

return'den önce bile finally çalışır: önce "finally in f", sonra dönen 1 basılır. h'de return x ifadesi değeri (10) o anda hesaplayıp saklar; finally'deki x = 20 saklanmış dönüş değerini değiştirmez: 10. (finally içine kendi return'ünü yazsaydın o kazanırdı, bu kötü bir alışkanlık.)

Soru 5orta

Find the compile error.

static void open(String name) {
    throw new FileNotFoundException(name);
}
Cevabı göster
Checked istisna ne yakalanmış ne throws ile bildirilmiş

FileNotFoundException, IOException'ın alt sınıfı, yani checked. javac: unreported exception FileNotFoundException; must be caught or declared to be thrown. Çözüm: imzaya throws FileNotFoundException ekle ya da try/catch ile sar.

Soru 6orta

Find the compile error.

try {
    int x = Integer.parseInt("12");
} catch (Exception e) {
    System.out.println("general");
} catch (NumberFormatException e) {
    System.out.println("number");
}
Cevabı göster
Alt sınıf catch'i üst sınıftan sonra: erişilemez

Exception her şeyi yakaladığı için ikinci catch'e hiçbir zaman sıra gelmez. javac: exception NumberFormatException has already been caught. Kural: özel (alt sınıf) catch'ler önce, genel olanlar sonra.

Soru 7zor

Find the compile error.

try {
    System.out.println("hi");
} catch (IOException e) {
    System.out.println("io");
}
Cevabı göster
try bloğu IOException fırlatamaz, catch geçersiz

Checked bir istisnayı, try içinde onu fırlatabilecek bir şey yoksa yakalayamazsın. javac: exception IOException is never thrown in body of corresponding try statement. Bu kural unchecked istisnalar ve Exception için geçmez: catch (Exception e) burada derlenirdi.

Soru 8kolay

Which exceptions are checked? (1) ArithmeticException (2) IOException (3) NullPointerException (4) FileNotFoundException (5) NumberFormatException

Cevabı göster
(2) ve (4)

RuntimeException'dan türeyenler unchecked: ArithmeticException, NullPointerException, NumberFormatException (IllegalArgumentException'ın alt sınıfı), ArrayIndexOutOfBoundsException, ClassCastException. IOException ve alt sınıfı FileNotFoundException doğrudan Exception'dan gelir: checked, derleyici yakalamanı ya da bildirmeni ister.

Soru 9orta

What is printed?

Exception ex = new IllegalArgumentException("bad value");
System.out.println(ex.getMessage());
System.out.println(ex);
System.out.println(new RuntimeException().getMessage());
Cevabı göster
bad value / java.lang.IllegalArgumentException: bad value / null

getMessage sadece mesajı verir. toString (println'in çağırdığı) tam sınıf adı + ": " + mesaj verir. Mesajsız yaratılan istisnanın getMessage'ı null döner.

Soru 10orta

What is printed?

static void parse(String s) {
    try {
        int n = Integer.parseInt(s);
        System.out.println(100 / n);
    } catch (NumberFormatException | ArithmeticException e) {
        System.out.println("bad input: " + e.getClass().getSimpleName());
    }
}
// in main:
parse("4"); parse("x"); parse("0");
Cevabı göster
25 / bad input: NumberFormatException / bad input: ArithmeticException

Multi-catch tek blokta birden fazla tipi yakalar. "4": 100 / 4 = 25. "x": parseInt NumberFormatException. "0": parse başarılı ama 100 / 0 ArithmeticException. Multi-catch'teki tipler birbirinin alt sınıfı olamaz; ArithmeticException | RuntimeException derlenmez.

Soru 11orta

What is printed?

class InsufficientFundsException extends Exception {
    private final double shortfall;
    InsufficientFundsException(String msg, double shortfall) {
        super(msg); this.shortfall = shortfall;
    }
    double getShortfall() { return shortfall; }
}
// Account.withdraw throws it when amount > balance, otherwise subtracts
Account acc = new Account(100);
try {
    acc.withdraw(30);
    acc.withdraw(100);
    System.out.println("not printed");
} catch (InsufficientFundsException e) {
    System.out.println(e.getMessage() + ", short by " + e.getShortfall());
}
System.out.println(acc.getBalance());
Cevabı göster
Not enough money, short by 30.0 / 70.0

İlk çekim başarılı, bakiye 70. İkinci çekimde 100 > 70: istisna fırlar, bakiye değişmez, "not printed" atlanır. Eksik miktar 100 − 70 = 30.0. super(msg) mesajı Exception'a iletir, getMessage onu döner.

Soru 12zor

Write a checked exception InvalidAgeException and a method int readAge(String text) that parses text (ignoring surrounding spaces) and throws InvalidAgeException with message not a number: <text> if it is not an integer, or out of range: <age> if it is outside 0 to 150.

Cevabı göster
extends Exception + throws + NumberFormatException'ı yakalayıp kendi istisnana çevir

Checked istisna için Exception'dan türet. parseInt'in unchecked istisnasını yakalayıp anlamlı bir checked istisnaya çevir:

public class InvalidAgeException extends Exception {
    public InvalidAgeException(String msg) { super(msg); }
}

public static int readAge(String text) throws InvalidAgeException {
    int age;
    try {
        age = Integer.parseInt(text.trim());
    } catch (NumberFormatException e) {
        throw new InvalidAgeException("not a number: " + text);
    }
    if (age < 0 || age > 150)
        throw new InvalidAgeException("out of range: " + age);
    return age;
}
"21" → 21, " 40 " → 40, "abc" → not a number: abc, "200" → out of range: 200. Çağıran yer try/catch yazmak zorunda; yazmazsa unreported exception InvalidAgeException.

Doğrulama: Tüm kod örnekleri ve cevaplar JDK 26 ile çalıştırıldı (java verify/SEN1002/W09.java; yakalanmayan istisna çıktısı da aynı dosyanın sonundan); derleme hataları javac ile denendi (verify/SEN1002/err/E09a-f.java).

Deftere not kartı

try { riskli } catch (Tip e) { çare } finally { HER ZAMAN } İstisna olunca try'ın KALANI atlanır. Eşleşen ilk catch çalışır (yukarıdan aşağı). Catch sırası: ALT sınıf önce. Exception önce yazılırsa "has already been caught". finally: istisna olsa da olmasa da, return olsa bile çalışır (System.exit hariç). return x; sonra finally'de x değişirse dönen değer DEĞİŞMEZ. Yakalanmazsa: metot anında biter, finally çalışır, çağırana geçer (stack unwinding). main'de de yakalanmazsa: Exception in thread "main" ... + stack trace, program biter. Throwable ← Error (yakalama) / Exception ← RuntimeException (unchecked) / IOException (checked). Unchecked: Arithmetic, NullPointer, ArrayIndexOutOfBounds, ClassCast, NumberFormat, IllegalArgument. Checked: IOException, FileNotFoundException, kendi "extends Exception" sınıfın. Checked → ya catch ya da imzada throws. Yoksa "unreported exception ... must be caught". throw new X("msg"); (fırlat, tekil) void m() throws X (bildir, imzada) Multi-catch: catch (A | B e) A ve B akraba olamaz. getMessage() → mesaj. toString() → sınıfAdı: mesaj. Mesajsız → null. Özel istisna: class MyEx extends Exception { MyEx(String m) { super(m); } }