Java ile Nesne Yönelimli Programlama · Ders Notu
Bölüm 6: İstisnalar
Bir program, yazıldığı gibi çalışsa bile dış dünyanın sürprizleriyle karşılaşır. Okunacak dosya silinmiş olabilir, kullanıcı sayı yerine harf girebilir, ağ bağlantısı kopabilir. Programın kendi içinde de hatalar olur: sıfıra bölme, dizinin dışına taşma, null bir referansın kullanılması. Java bu durumların hepsini tek bir düzenekle ele alır: istisna. Not önce istisnanın ne olduğunu ve yakalanmadığında programa ne olduğunu gösterir. Ardından try, catch ve finally bloklarını, kaynakların otomatik kapatılmasını ve iç içe blokları adım adım izler. İstisna sınıflarının hiyerarşisi, kontrol edilen ve kontrol edilmeyen istisnalar, throw ve throws anahtar kelimeleri notun merkezindedir. Yaygın istisna sınıfları tek tek örneklenir. Not özel istisna sınıfları yazmak, izleme listesini okumak ve iyi uygulamalarla biter.
Öğrenme çıktıları
- Kavram
- İstisnayı tanımlamak; çalışma zamanı sorunlarını
ExceptionveErrorolarak ayırt etmek; dönüş kodu yerine istisna kullanmanın gerekçesini açıklamak. - Yakalama
try,catch,finallyve try-with-resources ile istisnaları ele almak; akışı adım adım izlemek.- Hiyerarşi
Throwable,Error,ExceptionveRuntimeExceptionarasındaki ilişkiyi vecatchsırasının önemini açıklamak.- Türler
- Kontrol edilen ve kontrol edilmeyen istisnaları ayırmak; “yakala ya da bildir” kuralını uygulamak.
- Fırlatma
throwile istisna fırlatmak,throwsile bildirmek; istisnanın çağrı yığınındaki yayılımını izlemek.- Uygulama
- Yaygın istisna sınıflarını tanımak, özel istisna sınıfı yazmak ve izleme listesini okuyarak hatanın kaynağını bulmak.
6.1İstisna kavramı
Programlama hataları üç gruba ayrılır. Derleme hatası, kodun dilin kurallarına uymamasıdır: noktalı virgül eksiktir ya da bir değişken tanımlanmamıştır. Derleyici bu hatayı bulur ve program hiç çalışmaz. Mantık hatası ise sessizdir: program çalışır, ama yanlış sonuç üretir. Ortalamayı hesaplarken toplamı yanlış sayıya bölmek buna örnektir. Üçüncü grup, program çalışırken ortaya çıkan sorunlardır. Kod derlenmiştir, ama belirli bir anda belirli bir değerle yürütülemez. Bu notun konusu bu üçüncü gruptur.
6.1.1İstisna nedir?
İstisna (exception), program çalışırken ortaya çıkan ve programın normal akışını bozan beklenmedik durumdur. Java'da istisna bir nesnedir. Bu nesne durumun tipini, açıklayıcı bir mesajı ve sorunun oluştuğu yeri taşır.
Gündelik örnekler hemen akla gelir. Açılmak istenen dosya bulunamaz. Bir tam sayı sıfıra bölünür. Üç öğeli bir dizinin beşinci öğesine erişilmek istenir. null değerli bir referans üzerinden metot çağrılır. Bu durumların hiçbiri derleyicinin önceden göreceği türden değildir. Dosyanın var olup olmadığı, paydanın sıfır olup olmadığı ancak program çalışırken belli olur.
İstisnayı iki taraf oluşturabilir. Birincisi Java sanal makinesidir (Java Virtual Machine, JVM). Sıfıra bölme ya da dizi sınırının aşılması gibi durumları JVM kendisi fark eder ve istisna nesnesini kendiliğinden üretir. İkincisi programcının kendisidir. Programcı, örneğin geçersiz bir argüman gördüğünde throw anahtar kelimesiyle bilerek bir istisna oluşturur. Her iki durumda da sonuç aynıdır: ortada bir istisna nesnesi vardır ve normal akış durmuştur.
İstisna düzeneği programcıya olağan dışı durumlarla düzenli biçimde başa çıkma olanağı verir. Kabaca söylersek, Java bir istisnayla karşılaştığında şöyle der: “Pes ettim, şu an ne yapacağımı bilmiyorum. Bunu biri halletsin!” Sorunu çözecek kod, sorunun çıktığı yerde olmak zorunda değildir.
Bir lokantayı düşünelim. Garson mutfağa gider ve istenen yemeğin bittiğini öğrenir. Garson bu sorunu kendi başına çözemez; durumu yöneticiye bildirir. Yönetici ya sorunu çözer ve müşteriye başka bir yemek önerir ya da durumu bir üst kademeye iletir. İstisna da metotlar arasında böyle yol alır. Sorunu fark eden metot onu çözemiyorsa kendisini çağıran metoda bildirir. Bu zincir, sorunu çözebilecek bir metoda ulaşana kadar sürer.
6.1.2Çalışma zamanı hataları: istisna ve Error
Program çalışırken ortaya çıkan sorunu JVM bir Throwable nesnesi olarak fırlatır. Bu sınıfın adı “fırlatılabilir” anlamına gelir. Throwable'ın iki ana kolu vardır ve iki kol çok farklı durumları anlatır.
| Kol | Ne anlatır? | Program ne yapabilir? | Örnek |
|---|---|---|---|
İstisna (Exception) | Programın öngörebileceği, olağan dışı bir durum | Yakalayıp toparlanabilir | 10 / 0, üç öğeli dizide dizi[5], olmayan dosya |
Hata (Error) | JVM ya da sistem düzeyinde ciddi bir sorun | Genellikle yakalanmaz; program sonlanır | StackOverflowError, OutOfMemoryError |
Tablodaki ayrımın ölçütü “program bu durumdan kurtulabilir mi?” sorusudur. Dosya bulunamadıysa program kullanıcıdan başka bir dosya adı isteyebilir. Kullanıcı sayı yerine harf girdiyse program uyarı verip yeniden sorabilir. Bunlar istisnadır. Bellek tükendiyse ya da çağrı yığını taştıysa programın elinde düzeltme yapacak kaynak kalmamıştır. Bunlar Error sınıfına girer.
İstisna düzeneği ve istisnanın bıraktığı izleme listesi, çalışma zamanı hatalarının nedenini bulup düzeltmenin, yani hata ayıklamanın (debugging) en güçlü araçlarındandır. Derleme hatasını derleyici gösterir; mantık hatası iz bırakmaz. Çalışma zamanı hatasında ise JVM sorunun tipini, mesajını ve tam yerini yazdırır.
6.1.3Yakalanmayan istisna
Önce hiçbir önlem alınmadığında ne olduğuna bakalım. Aşağıdaki program bir tam sayıyı sıfıra böler.
public class Cokus {
public static void main(String[] args) {
int pay = 10, payda = 0;
System.out.println("Başla");
int sonuc = pay / payda;
System.out.println("Sonuç: " + sonuc);
System.out.println("Bitti");
}
}Başla
Exception in thread "main" java.lang.ArithmeticException: / by zero
at Cokus.main(Cokus.java:5)Program önce Başla yazar. 5. satırda pay / payda ifadesi hesaplanırken payda sıfırdır. Tam sayı bölmesinde sıfıra bölmenin bir sonucu yoktur. JVM bir ArithmeticException nesnesi oluşturur ve onu fırlatır. Bu satırda istisnayı yakalayacak bir kod yoktur. main metodunu çağıran da JVM'in kendisidir. Bu yüzden istisna JVM'e kadar ulaşır ve program sonlanır. Sonuç ve Bitti satırları hiç yazılmaz.
JVM'in yazdırdığı ileti üç bilgi taşır. Exception in thread "main" kısmı, istisnanın main iş parçacığında yakalanmadığını söyler. Ardından istisnanın tam sınıf adı gelir: java.lang.ArithmeticException. İki noktadan sonraki / by zero istisnanın mesajıdır. Son satır sorunun yerini verir: Cokus sınıfının main metodu, Cokus.java dosyasının 5. satırı. at ile başlayan bu satırlara izleme listesi denir.
Programın “çöktüğünü” görünce derleme hatası sanmak. Bu program sorunsuz derlenir. Derleyici payda değişkeninin çalışma sırasında hangi değeri alacağını bilmez. Sorun yalnız çalıştırınca ortaya çıkar. İletideki Exception in thread ifadesi bunun çalışma zamanı sorunu olduğunu gösterir.
6.1.4Ele almak ya da bildirmek
Bir metotta istisna oluşabiliyorsa metodun iki seçeneği vardır.
Birinci yol, istisnayı ele almaktır (handle). Metot, istisna oluşturabilecek kodu bir try bloğuna alır ve catch bloğunda durumu kendisi çözer. Örneğin ayar dosyasını okuyan bir metot düşünelim. Dosya bulunamazsa (FileNotFoundException) metot kullanıcıya bir mesaj gösterir ve varsayılan ayarlarla devam eder.
İkinci yol, istisnayı çağıranın sorunu yapmaktır (declare). Metot istisnayı ele almaz; imzasında throws anahtar kelimesiyle bu istisnayı fırlatabileceğini bildirir. Böylece istisna, metodu çağıran kodun sorunu olur. Çağıran kod da aynı seçimle karşılaşır: ya istisnayı ele alır ya da bir üst metoda iletir.
Hangi yolun seçileceğine şu soru karar verir: istisnayı anlamlı biçimde çözebilecek yer neresidir? Dosya adını kullanıcıdan alan kod, “dosya yok” durumunda kullanıcıya yeniden sorabilir. Dosyayı satır satır okuyan alt düzey bir metot ise kullanıcıyla konuşmaz; dosya adının nereden geldiğini de bilmez. Bu metot için doğru seçim istisnayı bildirmek, kullanıcı arayüzü kodu için doğru seçim ise onu ele almaktır.
6.1.5Dönüş kodu yerine istisna
İstisnalar, bir programın “bir şeyin ters gittiğini” bildirmesi için kullanılır. Ama “ters gitmek” özneldir. Bir dizide aranan ismin bulunamaması bir hata mıdır, yoksa olağan bir sonuç mu? İstisna düzeneği yaygınlaşmadan önce bu tür durumlar özel bir dönüş değeriyle bildirilirdi. Aşağıdaki metot, aranan ismi bulamazsa -1 döndürür.
public static int indexOf(String[] isimler, String isim) {
for (int i = 0; i < isimler.length; i++) {
if (isimler[i].equals(isim)) {
return i;
}
}
return -1;
}
// main içinde
String[] isimler = {"Ali", "Ayşe", "Can"};
System.out.println(indexOf(isimler, "Can"));
int k = indexOf(isimler, "Ece"); // k = -1
System.out.println(isimler[k]);2
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 3
at DonusKodu.main(DonusKodu.java:14)İlk çağrı "Can" ismini 2. indiste bulur ve 2 yazdırılır. İkinci çağrıda "Ece" dizide yoktur ve metot -1 döndürür. Çağıran kod bu değeri denetlemeyi unutmuştur. isimler[-1] erişimi bir ArrayIndexOutOfBoundsException fırlatır. Asıl sorun aramada çıkmıştır, ama hata başka bir yerde, dizi erişiminde görünür. Hatanın kaynağıyla belirtisinin ayrı yerlerde olması hata ayıklamayı zorlaştırır.
Dönüş kodunun bir sorunu daha vardır: hata değeri olağan sonuçlarla karışabilir. -1 burada geçersiz bir indistir, bu yüzden işe yarar. Ama sıcaklık ölçen bir metot için -1 geçerli bir değerdir. Aynı metodun bir hatayı bildirmek için kullanabileceği boş bir değer kalmaz.
Aynı metot istisnayla şöyle yazılır.
public static int bul(String[] isimler, String isim) {
for (int i = 0; i < isimler.length; i++) {
if (isimler[i].equals(isim)) {
return i;
}
}
throw new IllegalArgumentException("Bulunamadı: " + isim);
}
// main içinde
try {
System.out.println(bul(isimler, "Ece"));
} catch (IllegalArgumentException e) {
System.out.println("Hata: " + e.getMessage());
}Hata: Bulunamadı: EceEşleşme bulunamazsa metot bir IllegalArgumentException fırlatır. Mesaj, aranan ismi de içerir: Bulunamadı: Ece. Artık çağıranın denetlemeyi unutması mümkün değildir. İstisna yakalanmazsa program o noktada durur ve sorunun yerini açıkça gösterir. Hata, normal sonuçla karışmaz: bul metodu bir sayı döndürdüyse bu sayı her zaman geçerli bir indistir.
Bu nedenle -1 gibi özel dönüş kodlarından genellikle kaçınılır ve Java'nın istisna düzeneği tercih edilir. İstisnaların yararları üç maddede toplanır:
- Hata işleme açık ve tutarlıdır; hata değeri normal sonuçla karışmaz.
- Kod daha okunaklı olur, hata ayıklama kolaylaşır: hata doğduğu yerde yakalanır.
- Kodun güvenilirliği artar: denetlenmeyen dönüş kodları gibi sessizce gözden kaçmaz.
Her olumsuz sonucu istisnaya çevirmek. Bir arama metodunun “bulunamadı” sonucu, programın olağan akışının bir parçası olabilir. Java kütüphanesindeki String.indexOf bu yüzden hâlâ -1 döndürür. İstisna, çağıranın beklemediği ve sıradan bir değerle anlatılamayan durumlar içindir. Karar, metodun sözleşmesine göre verilir ve belgelenir.
6.2try ve catch
6.2.1try ve catch deyimleri
Java'da hata yönetiminin temel yapısı try-catch deyimidir. Hata çıkarabilecek kod try bloğuna yazılır. Hata çıkarsa ne yapılacağı ise catch bloğuna yazılır.
try bloğu, istisna oluşturabilecek deyimleri içerir. catch bloğu, try bloğunda belirtilen tipte bir istisna oluştuğunda çalışır ve onu ele alır; buna istisnayı yakalamak denir. catch'in parantezindeki (ArithmeticException e) bir parametre bildirimidir: e, fırlatılan istisna nesnesini gösteren bir referanstır.
try {
// Potansiyel hata içeren kodlar
int sonuc = 10 / 0; // Hata: ArithmeticException
} catch (ArithmeticException e) {
// Hata durumuyla başa çıkma
System.out.println("hata oluştu: " + e.getMessage());
}hata oluştu: / by zero10 / 0 ifadesi bir ArithmeticException fırlatır. Fırlatılan nesnenin tipi catch'te yazılan tiple eşleşir. JVM e parametresine bu nesneyi bağlar ve catch bloğunu çalıştırır. e.getMessage() nesnenin mesajını, / by zero metnini döndürür.
Bu yapının akışı iki kuralla özetlenir:
- İstisna oluşursa
trybloğunun kalan satırları atlanır ve denetim eşleşencatchbloğuna geçer. - İstisna oluşmazsa
catchbloğu hiç çalışmaz.
Her iki durumda da catch bloğundan sonra program normal akışına devam eder. İstisna yakalandığı için program çökmez.
try-catch akışının iki yolu. İstisna yoksa try bitince doğrudan sonraki koda geçilir. İstisna varsa önce catch çalışır, sonra sonraki koda geçilir.Burada kullanılan kod, istisnayı göstermek için yazılmış yapay bir örnektir. Değişmez 10 / 0 ifadesinde derleyici de sorunu görür ve bir uyarı verir (division by zero). Gerçek programlarda payda çoğunlukla bir değişkendir ve değeri ancak çalışırken belli olur.
6.2.2Kod izleme: try-catch akışı
Yakalanmayan istisna örneğindeki program bu kez bir try-catch içine alınmıştır.
public class BolmeOrnegi {
public static void main(String[] args) {
int pay = 10, payda = 0;
System.out.println("Başla");
try {
System.out.println("Bölme öncesi");
int sonuc = pay / payda;
System.out.println("Sonuç: " + sonuc);
} catch (ArithmeticException e) {
System.out.println("Hata: " + e.getMessage());
}
System.out.println("Bitti");
}
}Program satır satır ilerler. Tablo 6.2, sunumdaki adım adım izlemenin her adımındaki satırı, değişkenleri ve o adımda eklenen çıktıyı verir.
| Adım | Satır | Çağrı yığını | Değişkenler | Yeni çıktı |
|---|---|---|---|---|
| 1 | 3: int pay = 10, payda = 0; | main() | pay=10, payda=0 | — |
| 2 | 4: System.out.println("Başla"); | main() | pay=10, payda=0 | Başla |
| 3 | 6: System.out.println("Bölme öncesi"); | main() | pay=10, payda=0 | Bölme öncesi |
| 4 | 7: int sonuc = pay / payda; | main() | pay=10, payda=0 | — |
| 5 | 9: } catch (ArithmeticException e) { | main() | pay=10, payda=0, e=ArithmeticException | — |
| 6 | 10: System.out.println("Hata: " + e.getMessage()); | main() | pay=10, payda=0, e=ArithmeticException | Hata: / by zero |
| 7 | 12: System.out.println("Bitti"); | main() | pay=10, payda=0 | Bitti |
İlk iki adımda istisnayla ilgili bir şey yoktur. Değişkenler oluşur ve try dışındaki ilk satır Başla yazar. 3. adımda try bloğuna girilir; ilk satır sorunsuz çalışır ve Bölme öncesi yazılır. 4. adım işin kalbidir. pay / payda hesaplanırken JVM bir ArithmeticException nesnesi oluşturur ve fırlatır. Atama yapılmaz; sonuc değişkeni hiç değer almaz.
try'ın ilk satırı başarıyla çalıştı (yeşil), ikinci satır istisna fırlattı (kırmızı), üçüncü satır atlandı (kesik çizgi). Tip eşleştiği için catch bloğu çalışıyor.5. adımda JVM uygun bir catch arar. Fırlatılan nesne bir ArithmeticException'dır ve catch'te de bu tip yazılıdır. Tip eşleşir, e istisna nesnesini gösterir. Sonuç satırı atlanmıştır. 6. adımda e.getMessage() çağrılır ve Hata: / by zero yazılır. 7. adımda catch bloğu biter ve program normal akışına döner: Bitti yazılır.
İki programı karşılaştırmak öğreticidir. try-catch olmadan program 5. satırda sonlanıyor ve Bitti yazılmıyordu. Şimdi aynı istisna oluşuyor, ama program sonuna kadar çalışıyor. İstisna yok olmadı; yalnız ele alındı.
try içinde tanımlanan değişkeni bloğun dışında kullanmak. int sonuc yukarıdaki programda try bloğunda tanımlıdır; kapsamı bloğun kapanış parantezinde biter. try'dan sonra System.out.println(sonuc) yazılırsa derleyici cannot find symbol hatası verir. Değişken dışarıda kullanılacaksa try'dan önce tanımlanır ve bir ilk değer verilir.
6.2.3İstisna nesnesinden bilgi almak
catch parametresi sıradan bir referanstır. İstisna nesnesi de sıradan bir nesnedir ve metotlarını Throwable sınıfından alır. Aşağıdaki kod bu metotların ne verdiğini gösterir.
try {
int[] dizi = new int[3];
dizi[5] = 7;
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println(e.getMessage());
System.out.println(e);
System.out.println(e.getClass().getSimpleName());
e.printStackTrace();
}Index 5 out of bounds for length 3
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
ArrayIndexOutOfBoundsException
java.lang.ArrayIndexOutOfBoundsException: Index 5 out of bounds for length 3
at IstisnaNesnesi.main(IstisnaNesnesi.java:5)| Metot | Ne verir? |
|---|---|
getMessage() | Yalnız açıklama mesajı; mesaj verilmemişse null |
toString() | Tam sınıf adı, “: ” ve mesaj; println(e) bunu kullanır |
getClass() | İstisnanın gerçek sınıfı; getSimpleName() ile paketsiz adı |
printStackTrace() | toString() ve izleme listesi; System.err'e yazar |
getCause() | Bu istisnaya yol açan başka istisna; yoksa null |
Çıktının ilk satırı getMessage()'dan gelir: Index 5 out of bounds for length 3. İkinci satırı println(e) yazar. println, nesnenin toString() metodunu çağırır ve sınıfın tam adıyla mesajı birleştirir. Üçüncü satır yalnız sınıfın kısa adıdır. Son iki satırı printStackTrace() üretir. Bu metot önce toString() sonucunu, ardından izleme listesini yazar.
printStackTrace() standart çıktıya değil, hata çıktısına (System.err) yazar. İki akış aynı konsolda görünse de ayrıdır. Bu yüzden iki akışa birlikte yazılan satırların sırası konsolda karışabilir. Çoğu geliştirme ortamı (IDE) hata akışını kırmızı renkte gösterir.
getCause() zincirlenmiş istisnalar içindir. Bir metot, alt düzeyde yakaladığı bir istisnayı kendi tipine çevirip yeniden fırlatabilir. Bunu yaparken eski istisnayı “neden” olarak yeni nesneye ekler: new RuntimeException("Ayarlar okunamadı", e). Böylece üst düzeydeki kod, sorunun asıl kaynağına getCause() ile ulaşabilir. Bu örnekte istisnanın bir nedeni yoktur ve metot null döndürür.
Kullanıcıya gösterilecek mesajda getMessage()'ın her zaman dolu olduğunu varsaymak. new Exception() gibi mesajsız oluşturulan istisnalarda getMessage() null döndürür. Kayıt tutarken e.toString() ya da println(e) daha güvenlidir: en azından sınıf adı her zaman yazılır.
6.2.4Birden çok catch bloğu ve sıra
Bir kod bloğunda birden çok tipte istisna oluşabilir. Kullanıcıdan alınan iki sayıyı bölen bir program düşünelim. Girdi sayı olmayabilir, payda sıfır olabilir, girdi eksik olabilir. Her hata türü için ayrı bir catch bloğu yazılır ve her blok o tipe özgü tepkiyi içerir.
try {
// Potansiyel hata içeren kodlar
} catch (ArithmeticException e) {
// ArithmeticException ile başa çıkma
} catch (NullPointerException e) {
// NullPointerException ile başa çıkma
} catch (Exception e) {
// Diğer hata türleri için genel durum
}İstisna fırlatıldığında catch blokları yukarıdan aşağıya sırayla denenir. Eşleşen ilk blok çalışır, diğerleri atlanır. Burada “eşleşmek”, fırlatılan nesnenin catch'teki tipin kendisi ya da bir alt sınıfı olması demektir. Bu, instanceof işlecinin mantığıdır. Son sıradaki catch (Exception e) geri kalan bütün istisnaları yakalayan genel durumdur.
NullPointerException için catch bloklarının denenmesi. Birinci blok eşleşmez, ikinci blok çalışır, üçüncü blok hiç denenmez.Sıra, catch tipleri arasında bir kalıtım ilişkisi olduğunda önem kazanır. Aşağıdaki kodda genel tip özel tipten önce yazılmıştır.
try {
int sonuc = 10 / 0;
} catch (Exception e) {
System.out.println("Genel hata");
} catch (ArithmeticException e) {
System.out.println("Aritmetik hata");
}SiraHatasi.java:7: error: exception ArithmeticException has already been caught
} catch (ArithmeticException e) {ArithmeticException bir Exception'dır. Fırlatılan her ArithmeticException'ı birinci blok zaten yakalar. İkinci blok hiçbir durumda çalışamaz. Java derleyicisi erişilemeyen bir catch bloğuna izin vermez ve hata verir. Kural basittir: alt sınıflar üst sınıflardan önce gelir. Doğru sıralama şöyledir.
} catch (ArithmeticException e) {
System.out.println("Aritmetik hata");
} catch (Exception e) {
System.out.println("Genel hata");
}Kalıtım ilişkisi olmayan tiplerde sıra önemli değildir. ArithmeticException ile NullPointerException birbirinin alt sınıfı değildir. Bir nesne aynı anda ikisi birden olamaz; bu yüzden iki blok hangi sırayla yazılırsa yazılsın en fazla biri eşleşir.
NumberFormatException, IllegalArgumentException'ın alt sınıfıdır. Önce catch (IllegalArgumentException e), sonra catch (NumberFormatException e) yazılırsa ne olur?
Derleme hatası oluşur: exception NumberFormatException has already been caught. Her NumberFormatException bir IllegalArgumentException olduğu için birinci blok onu zaten yakalar. İki bloğun yeri değiştirilirse kod derlenir. Önce özel durum (“sayı değil”), sonra genel durum (“geçersiz argüman”) ele alınır.
6.2.5Kod izleme: hangi catch çalışır?
Aşağıdaki metot bir metin dizisinden iki sayı okur ve birini ötekine böler. Üç ayrı catch bloğu ve bir finally bloğu vardır. finally bloğu, istisna olsun olmasın her durumda çalışan bloktur; burada her çağrının sonunda bir satır yazar.
static void bol(String[] girdi) {
try {
int x = Integer.parseInt(girdi[0]);
int y = Integer.parseInt(girdi[1]);
System.out.println("Sonuç: " + (x / y));
} catch (NumberFormatException e) {
System.out.println("Sayı değil: " + e.getMessage());
} catch (ArithmeticException e) {
System.out.println("Sıfır: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Eksik: " + e.getMessage());
} finally {
System.out.println("finally çalıştı");
}
}
// main içinde
bol(new String[] {"10", "abc"});
bol(new String[] {"10", "0"});
bol(new String[] {"10"});Sayı değil: For input string: "abc"
finally çalıştı
Sıfır: / by zero
finally çalıştı
Eksik: Index 1 out of bounds for length 1
finally çalıştı| Adım | Satır | Çağrı yığını | Değişkenler | Yeni çıktı |
|---|---|---|---|---|
| 1 | 4: int y = Integer.parseInt(girdi[1]); | main() → bol(girdi) | girdi=["10", "abc"], x=10, y=— | — |
| 2 | 6: } catch (NumberFormatException e) {7: System.out.println("Sayı değil: " + e.getMessage()); | main() → bol(girdi) | girdi=["10", "abc"], x=10, y=— | Sayı değil: For input string: "abc" |
| 3 | 12: } finally { | main() → bol(girdi) | girdi=["10", "abc"], x=10, y=— | finally çalıştı |
| 4 | 5: System.out.println("Sonuç: " + (x / y)); | main() → bol(girdi) | girdi=["10", "0"], x=10, y=0 | — |
| 5 | 8: } catch (ArithmeticException e) {9: System.out.println("Sıfır: " + e.getMessage()); | main() → bol(girdi) | girdi=["10", "0"], x=10, y=0 | Sıfır: / by zero |
| 6 | 4: int y = Integer.parseInt(girdi[1]); | main() → bol(girdi) | girdi=["10"], x=10, y=— | finally çalıştı |
| 7 | 10: } catch (ArrayIndexOutOfBoundsException e) {11: System.out.println("Eksik: " + e.getMessage());12: } finally { | main() → bol(girdi) | girdi=["10"], x=10, y=— | Eksik: Index 1 out of bounds for length 1 ⏎ finally çalıştı |
Birinci çağrıda girdi dizisi {"10", "abc"}'dir. x 10 olur. Integer.parseInt("abc") ise bir NumberFormatException fırlatır. Bölme satırı atlanır. JVM catch bloklarını sırayla dener. İlk blok NumberFormatException içindir ve eşleşir. Diğer bloklara bakılmaz bile. Ardından finally çalışır.
İkinci çağrıda iki sayı da geçerlidir: x = 10, y = 0. Bölme ArithmeticException fırlatır. Birinci catch eşleşmez, ikinci catch eşleşir. Mesaj yine / by zero'dur.
ArrayIndexOutOfBoundsException. İlk iki blok eşleşmez, üçüncüsü eşleşir; ardından finally çalışır.Üçüncü çağrıda dizinin tek öğesi vardır. girdi[1] erişimi bir ArrayIndexOutOfBoundsException fırlatır. Mesaj, hangi indisin hangi uzunluktaki dizide geçersiz olduğunu söyler: Index 1 out of bounds for length 1. İlk iki catch eşleşmez, üçüncüsü eşleşir; sonra finally çalışır.
Üç çağrıda üç farklı istisna oluştu ve her birini ayrı bir blok karşıladı. İstisna oluşmasaydı ne olurdu? bol(new String[] {"10", "4"}) çağrısında hiçbir catch çalışmaz. Metot Sonuç: 2 ve finally çalıştı yazar. Tam sayı bölmesinde 10 / 4 = 2'dir.
6.2.6Tek catch, birden çok tip
Bazen farklı istisnalara aynı tepki verilir. Her biri için ayrı ve özdeş bir catch yazmak kod tekrarına yol açar. Java 7'den beri tipler tek bir catch'te | işaretiyle birleştirilebilir. Bu yapıya çoklu yakalama (multi-catch) denir.
try {
Object o = "Merhaba";
Integer i = (Integer) o;
} catch (ClassCastException
| NullPointerException e) {
System.out.println("Tür veya null hatası: "
+ e.getClass().getSimpleName());
}Tür veya null hatası: ClassCastException"Merhaba" bir String nesnesidir; Integer'a dönüştürülemez. Fırlatılan ClassCastException, bloktaki iki tipten birine uyar ve blok çalışır. e.getClass().getSimpleName() gerçekte hangi istisnanın yakalandığını gösterir.
Çoklu yakalamanın iki kuralı vardır. Birincisi, bu blokta e örtük olarak final sayılır; ona yeni bir değer atanamaz. e = null; yazılırsa derleyici multi-catch parameter e may not be assigned hatası verir. Tek tipli bir catch'te aynı atama yasak değildir, ama iyi bir alışkanlık da değildir.
İkinci kural, alternatiflerin birbirinin alt sınıfı olamamasıdır.
} catch (FileNotFoundException
| IOException e) {Ana.java:9: error: Alternatives in a multi-catch statement cannot be related by subclassing
| IOException e) {
Alternative FileNotFoundException is a subclass of alternative IOExceptionFileNotFoundException, IOException'ın alt sınıfıdır. IOException yazmak alt tipi zaten kapsar; ikisini birlikte yazmak anlamsızdır. Çözüm yalnız üst tipi yazmaktır: catch (IOException e).
6.3finally ve kaynak yönetimi
6.3.1finally bloğu
Bir program dosya açar, veri okur ve dosyayı kapatır. Okuma sırasında bir istisna oluşursa ne olur? try bloğunun kalanı atlanır; kapatma satırı da try'ın içindeyse hiç çalışmaz. Dosya açık kalır. İşletim sistemi bir programın aynı anda açabileceği dosya sayısını sınırlar. Kapatılmayan dosyalar, ağ bağlantıları ve veri tabanı bağlantıları birikir ve sonunda program yeni kaynak açamaz hâle gelir. Bu soruna kaynak sızıntısı denir.
finally bloğu (finally block), try ve catch bloklarının ardından yazılan ve her durumda çalışan bloktur. İstisna oluşsun ya da oluşmasın, istisna yakalansın ya da yakalanmasın, finally çalışır.
try {
// Potansiyel hata içeren kodlar
} catch (Exception e) {
// Diğer hata türleri için genel durum
} finally {
// Her durumda çalıştırılacak kodlar
}finally'nin “her durumda” çalışması dört durumu kapsar:
- İstisna yoktur:
trybiter,finallyçalışır. - İstisna vardır ve bir
catchyakalar:catchbiter,finallyçalışır. - İstisna vardır ama hiçbir
catcheşleşmez:finallyçalışır, ardından istisna metottan dışarı çıkar. tryya dacatchiçindereturnvardır:finally, metottan çıkmadan hemen önce çalışır.
Bu güvence, belirli adımların kesinlikle çalışmasını sağlar. Tipik kullanım temizlik işleridir: dosyaları, ağ ve veri tabanı bağlantılarını kapatmak, kilitleri bırakmak, geçici dosyaları silmek.
Bir try'dan sonra en az bir catch ya da bir finally gelmelidir. Yalnız try { } finally { } yazmak geçerlidir. Bu biçimde istisna yakalanmaz, ama temizlik yine yapılır. Tek başına bir try ise derleme hatasıdır: 'try' without 'catch', 'finally' or resource declarations.
Kuralın tek istisnası System.exit() çağrısıdır. Bu çağrı JVM'i hemen durdurur. try içinde System.exit(0) çalışırsa finally çalışmaz. JVM'in kendisinin çökmesi ya da bilgisayarın kapanması da elbette finally'yi engeller.
6.3.2Kod izleme: finally ve return
finally'nin en şaşırtıcı yanı return ile ilişkisidir. Aşağıdaki metotta try da catch da bir değer döndürür.
static int bol(int a, int b) {
try {
System.out.println("try başladı");
int s = a / b;
System.out.println("try bitti");
return s;
} catch (ArithmeticException e) {
System.out.println("catch: " + e.getMessage());
return -1;
} finally {
System.out.println("finally");
}
}
public static void main(String[] args) {
System.out.println("Sonuç = " + bol(10, 2));
System.out.println("Sonuç = " + bol(10, 0));
}try başladı
try bitti
finally
Sonuç = 5
try başladı
catch: / by zero
finally
Sonuç = -1| Adım | Satır | Çağrı yığını | Değişkenler | Yeni çıktı |
|---|---|---|---|---|
| 1 | 15: System.out.println("Sonuç = " + bol(10, 2)); | main() | — | — |
| 2 | 3: System.out.println("try başladı"); | main() → bol(10, 2) | a=10, b=2 | try başladı |
| 3 | 4: int s = a / b;5: System.out.println("try bitti"); | main() → bol(10, 2) | a=10, b=2, s=5 | try bitti |
| 4 | 6: return s; | main() → bol(10, 2) | a=10, b=2, s=5 | — |
| 5 | 11: System.out.println("finally"); | main() → bol(10, 2) | a=10, b=2, s=5 | finally |
| 6 | 15: System.out.println("Sonuç = " + bol(10, 2)); | main() | — | Sonuç = 5 |
| 7 | 16: System.out.println("Sonuç = " + bol(10, 0)); | main() | — | — |
| 8 | 3: System.out.println("try başladı"); | main() → bol(10, 0) | a=10, b=0 | try başladı |
| 9 | 4: int s = a / b; | main() → bol(10, 0) | a=10, b=0 | — |
| 10 | 8: System.out.println("catch: " + e.getMessage()); | main() → bol(10, 0) | a=10, b=0, e=ArithmeticException | catch: / by zero |
| 11 | 9: return -1; | main() → bol(10, 0) | a=10, b=0, e=ArithmeticException | — |
| 12 | 11: System.out.println("finally"); | main() → bol(10, 0) | a=10, b=0 | finally |
| 13 | 16: System.out.println("Sonuç = " + bol(10, 0)); | main() | — | Sonuç = -1 |
1. adımda println'ın argümanı hesaplanmalıdır. Bunun için önce bol(10, 2) çağrılır. try başlar, bölme başarılıdır ve s = 5 olur. 4. adımda return s çalışır. Dönüş değeri olan 5 hazırlanır, ama metot hemen bitmez. Metottan çıkmadan önce finally bloğu çalışmalıdır. 5. adımda finally yazılır. catch ise atlanmıştır, çünkü istisna yoktur. Ancak 6. adımda bekleyen 5 döner ve Sonuç = 5 yazılır.
try bloğu return s ile bitti; dönüş değeri 5 bekletiliyor. Metot ancak finally çalıştıktan sonra döner.İkinci çağrı bol(10, 0)'dır. try başladı yazılır. 9. adımda 10 / 0 istisna fırlatır. try'ın kalanı, yani try bitti satırı ve return s atlanır. catch eşleşir ve catch: / by zero yazar. 11. adımda return -1 çalışır: değer hazırdır, ama yine önce finally çalışır. Sonunda -1 döner. Sonuç açıktır: finally iki durumda da, dönüşten hemen önce çalıştı.
Bir metotta int s = 1; tanımlıdır. try içinde s = 2; return s;, finally içinde s = 3; ve System.out.print(s + " "); yazılıdır. System.out.println(g()) ne yazdırır?
3 2. return s; çalıştığında dönüş değeri hesaplanır ve saklanır: 2. Ardından finally çalışır, s'yi 3 yapar ve 3 yazar. Ama saklanmış dönüş değeri bir kopyadır; s'deki değişiklik onu etkilemez. Metot 2 döndürür. finally içinde ayrıca bir return olsaydı o değer dönerdi. Bu kullanım bekleyen değeri ve hatta bekleyen istisnayı sessizce siler; bu yüzden önerilmez.
finally içine return yazmak. try bir istisna fırlatmış olsa bile finally'deki return bu istisnayı yok eder ve metot sanki her şey yolundaymış gibi bir değer döndürür. Hata iz bırakmadan kaybolur. finally yalnız temizlik içindir; sonuç üretmez.
6.3.3try-with-resources
finally ile kaynak kapatmak doğrudur, ama kod uzar. Kaynak değişkeni try'dan önce null olarak tanımlanır, finally'de null olup olmadığı denetlenir, close() çağrılır. close() de bir istisna fırlatabildiği için çoğu zaman bir try-catch daha gerekir. Java 7 bu işi kısaltan bir yapı getirdi.
try-with-resources, try anahtar kelimesinden sonraki parantezde kaynak açan try deyimidir. Parantezde açılan kaynak, blok bitince otomatik olarak kapatılır: JVM kaynağın close() metodunu çağırır. Kapatma, blok istisnayla bitse bile yapılır. Kaynak sınıfı AutoCloseable arayüzünü uygulamalıdır.
Sunumdaki örnek, açılışını ve kapanışını ekrana yazan küçük bir kaynak sınıfı kullanır.
class Kaynak implements AutoCloseable {
private String ad;
Kaynak(String ad) {
this.ad = ad;
System.out.println(ad + " açıldı");
}
void kullan(int x) {
System.out.println(ad + " kullanılıyor");
int s = 10 / x;
}
@Override
public void close() {
System.out.println(ad + " kapatıldı");
}
}try (Kaynak k = new Kaynak("dosya")) {
k.kullan(0);
} catch (ArithmeticException e) {
System.out.println("Hata: " + e.getMessage());
}dosya açıldı
dosya kullanılıyor
dosya kapatıldı
Hata: / by zeroÇıktının sırası dikkat ister. Önce kaynak açılır ve kullanılır. kullan(0) içindeki bölme istisna fırlatır. Bu noktada catch'e geçilmeden önce kaynak kapatılır: dosya kapatıldı. Ancak ondan sonra catch çalışır. Yani kaynak, catch'ten önce kapanır.
Java kütüphanesinde kaynak işi gören sınıfların çoğu AutoCloseable'ı uygular: dosya akışları, Scanner, NIO kanalları, veri tabanı bağlantıları. Bu sınıflarla finally ve null denetimi yazmaya gerek kalmaz. try-with-resources, kaynak kapatmak için finally yazmanın modern ve güvenli yoludur.
AutoCloseable'ı uygulamayan bir sınıf parantezde kullanılamaz. kapat() adlı bir metodu olsa bile derleyici şu hatayı verir: incompatible types: try-with-resources not applicable to variable type. JVM'in çağıracağı metodun adı ve imzası arayüzle sabittir: close().
close() metodunun bildirdiği istisnayı unutmak. AutoCloseable.close(), throws Exception ile bildirilmiştir. Bir sınıf close()'u bu bildirimi koruyarak yazarsa try-with-resources'ın kendisi kontrol edilen bir istisna fırlatabilir hâle gelir. Derleyici unreported exception Exception hatası verir ve örtük close() çağrısını gösterir. Sunumdaki Kaynak sınıfı close()'u throws olmadan yazdığı için bu sorun çıkmaz.
6.3.4Kod izleme: iki kaynağın açılış ve kapanış sırası
Parantezde birden çok kaynak açılabilir; kaynaklar noktalı virgülle ayrılır.
try (Kaynak a = new Kaynak("dosya");
Kaynak b = new Kaynak("ağ")) {
b.kullan(0);
System.out.println("try sonu");
} catch (ArithmeticException e) {
System.out.println("Hata: " + e.getMessage());
} finally {
System.out.println("finally");
}dosya açıldı
ağ açıldı
ağ kullanılıyor
ağ kapatıldı
dosya kapatıldı
Hata: / by zero
finally| Adım | Satır | Değişkenler | Yeni çıktı |
|---|---|---|---|
| 1 | 1: try (Kaynak a = new Kaynak("dosya"); | — | dosya açıldı |
| 2 | 2: Kaynak b = new Kaynak("ağ")) { | — | ağ açıldı |
| 3 | 3: b.kullan(0); | — | ağ kullanılıyor |
| 4 | 3: b.kullan(0); | — | — |
| 5 | 2: Kaynak b = new Kaynak("ağ")) { | — | ağ kapatıldı |
| 6 | 1: try (Kaynak a = new Kaynak("dosya"); | — | dosya kapatıldı |
| 7 | 5: } catch (ArithmeticException e) {6: System.out.println("Hata: " + e.getMessage()); | — | Hata: / by zero |
| 8 | 8: System.out.println("finally"); | — | finally |
Kaynaklar yazılış sırasıyla açılır: önce dosya, sonra ağ. 3. adımda b.kullan(0) önce ağ kullanılıyor yazar, sonra 10 / 0 istisnası fırlatılır. try sonu satırı atlanır. Ama denetim hemen catch'e geçmez; önce kaynaklar kapatılır.
catch var.Kapatma ters sırayla yapılır. Son açılan b önce kapanır, sonra a kapanır. Bunun nedeni kaynaklar arasındaki olası bağımlılıktır. İkinci kaynak çoğu zaman birinciyi kullanır; örneğin bir okuyucu bir dosya akışını sarar. Önce dıştaki sarmalayıcı kapatılır, sonra içteki akış. İki kaynak da kapandıktan sonra catch çalışır, en son finally. Sıra şöyle özetlenir: aç, kullan, ters sırayla kapat, catch, finally.
İstisna olmasaydı sıra nasıl olurdu? b.kullan(5) çağrısında bölme başarılıdır. try sonu yazılır, ardından kaynaklar yine ters sırayla kapanır ve finally çalışır:
dosya açıldı
ağ açıldı
ağ kullanılıyor
try sonu
ağ kapatıldı
dosya kapatıldı
finally6.3.5İç içe try blokları
Bir try bloğunun içinde başka bir try bloğu bulunabilir. Bu yapı, bir işin bir bölümüne özel önlem alırken bütün işe genel bir önlem almak için kullanılır.
try {
// Dıştaki try bloğu
try {
// İçteki try bloğu
// Potansiyel hata içeren kodlar
} catch (Exception e) {
// İçteki catch bloğu
} finally {
// İçteki finally bloğu
}
} catch (Exception e) {
// Dıştaki catch bloğu
} finally {
// Dıştaki finally bloğu
}İçteki try'da bir istisna oluşursa JVM şu sırayla ilerler:
- İçteki
try'ıncatchblokları denenir. Uygun bircatchvarsa o çalışır. - Uygun
catchyoksa istisna dışa doğru ilerler. Dıştry'ıncatchblokları denenir ve ilk uygun olan çalışır. - Hiçbir
catcheşleşmezse istisna metottan çıkar. Sonunda kimse yakalamazsa JVM'in varsayılan işleyicisi programı sonlandırır.
Her durumda, istisnanın geçtiği her düzeydeki finally bloğu çalışır.
6.3.6Kod izleme: istisna dışa doğru ilerler
public static void main(String[] args) {
try {
System.out.println("Dış try");
try {
System.out.println("İç try");
String s = null;
System.out.println(s.length());
} catch (ArithmeticException e) {
System.out.println("İç catch");
} finally {
System.out.println("İç finally");
}
System.out.println("Dış try devam");
} catch (NullPointerException e) {
System.out.println("Dış catch: NPE");
} finally {
System.out.println("Dış finally");
}
System.out.println("Program sürüyor");
}Dış try
İç try
İç finally
Dış catch: NPE
Dış finally
Program sürüyor| Adım | Satır | Çağrı yığını | Değişkenler | Yeni çıktı |
|---|---|---|---|---|
| 1 | 3: System.out.println("Dış try"); | main() | — | Dış try |
| 2 | 5: System.out.println("İç try");6: String s = null; | main() | s=null | İç try |
| 3 | 7: System.out.println(s.length()); | main() | s=null | — |
| 4 | 8: } catch (ArithmeticException e) { | main() | s=null | — |
| 5 | 11: System.out.println("İç finally"); | main() | s=null | İç finally |
| 6 | 14: } catch (NullPointerException e) {15: System.out.println("Dış catch: NPE"); | main() | e=NullPointerException | Dış catch: NPE |
| 7 | 17: System.out.println("Dış finally"); | main() | — | Dış finally |
| 8 | 19: System.out.println("Program sürüyor"); | main() | — | Program sürüyor |
Dış try başlar, ardından iç try. s değişkeni null değerini alır. 3. adımda s.length() çağrılır. null bir referans üzerinden metot çağrılamaz; JVM bir NullPointerException fırlatır. İç catch yalnız ArithmeticException yakalar ve eşleşmez. İstisna iç düzeyden çıkmadan önce iç finally çalışır: İç finally.
try'dan çıktı; iç catch eşleşmedi, iç finally çalıştı. Dış try devam satırı atlandı ve dış catch eşleşti.6. adımda istisna dış düzeye geçmiştir. Dış try'da iç bloğun ardından gelen Dış try devam satırı atlanır. Dış catch NullPointerException içindir ve eşleşir. Ardından dış finally çalışır. İstisna ele alındığı için program normal akışına döner ve Program sürüyor yazılır.
İç try'da Object o = "12"; Integer i = (Integer) o; çalışsın. İç catch NumberFormatException, dış catch ClassCastException yakalasın. Her düzeyde bir finally ve dış try'ın sonunda dış try sonu yazan bir satır bulunsun. Program ne yazdırır?
iç finally, dış catch, dış finally, son. o bir String nesnesidir. "12" bir sayıya benzese de dönüşüm metni sayıya çevirmez; ClassCastException fırlatır. İç catch bu tipi yakalamaz. İstisna dışa ilerlerken iç finally çalışır. dış try sonu atlanır, dış catch yakalar, dış finally çalışır ve program sürer.
6.4İstisna hiyerarşisi
6.4.1Throwable ağacı
Java'da istisna sınıfları bir sınıf hiyerarşisi içindedir. Ağacın kökünde Throwable sınıfı bulunur. Throwable, fırlatılabilen her şeyin atasıdır. Yalnız Throwable'ın ve alt sınıflarının nesneleri throw ile fırlatılabilir ve catch ile yakalanabilir. throw "hata"; gibi bir satır bu yüzden derlenmez: incompatible types: String cannot be converted to Throwable.
Throwable'ın iki doğrudan alt sınıfı vardır: Error ve Exception. Exception'ın alt sınıflarından biri olan RuntimeException ise ayrı bir aile oluşturur. Şekil 6.9 bu yapıyı gösterir.
RuntimeException ve alt sınıflarıdır; kırmızı kutular Error ve alt sınıflarıdır. Bu iki aile kontrol edilmeyen istisnaları oluşturur.Hiyerarşinin pratik bir sonucu vardır: bir catch bloğu, yazılan tipi ve onun bütün alt sınıflarını yakalar. Bu, kalıtımın doğal sonucudur. Her FileNotFoundException aynı zamanda bir IOException'dır. Bu yüzden catch (IOException e) hem FileNotFoundException'ı hem de diğer giriş/çıkış hatalarını yakalar. Aynı aileden istisnalar tek bir üst tiple yakalanabilir.
Ağaçtaki birkaç ilişki ayrıca not edilmeye değer. NumberFormatException, IllegalArgumentException'ın alt sınıfıdır. ArrayIndexOutOfBoundsException ve StringIndexOutOfBoundsException, IndexOutOfBoundsException'ın alt sınıflarıdır. StackOverflowError ve OutOfMemoryError doğrudan Error'dan değil, onun alt sınıfı VirtualMachineError'dan türer. Şekil bu ara düzeyi sadelik için göstermez.
Java, yeni istisna sınıfları yazmayı da destekler. Uygulamaya özgü bir durum, örneğin “bakiye yetersiz”, kendi sınıfıyla bu hiyerarşiye eklenir. Bu sınıflar da ağacın bir dalına bağlanır ve aynı kurallara uyar.
new FileReader("yok.txt") bir FileNotFoundException fırlatıyor. RuntimeException, Error, SQLException ve IOException tiplerinden hangisi bu istisnayı yakalar?
Yalnız IOException. İstisnanın üst sınıf zinciri FileNotFoundException → IOException → Exception → Throwable'dır. RuntimeException ve SQLException Exception'ın ayrı dallarıdır. Error ise Exception ailesinden değildir. catch (Exception e) ya da catch (Throwable t) da yakalardı, ama seçenekler arasında yoktur.
6.4.2Error ve Exception
Ağacın iki büyük kolu farklı soruları yanıtlar.
Error, programın kurtarılamayacak ciddi bir sorunla karşılaştığını belirtir. Bu durumlarla başa çıkmak çoğunlukla mümkün değildir ve program genellikle sonlanır. Exception ise programın öngörebileceği ve çoğu zaman kurtarabileceği durumları temsil eder; try-catch ile ele alınması beklenen tip budur.
Error örnekleri aşırı durumlardır: program belleğinin tükenmesi (OutOfMemoryError), çağrı yığınının taşması (StackOverflowError), disk sürücüsünün “kaybolması”. Bu durumlar programın bir satırından çok, ortamın durumundan kaynaklanır. Uygulama kodunda Error yakalanması önerilmez. Bellek tükenmişse catch bloğunun çalışacak belleği de kalmamış olabilir.
Exception örnekleri ise gündelik sorunlardır: dosya yok, ağ koptu, girdi geçersiz, referans null. Bunların bir bölümü programın dışından gelir; bir bölümü programcının hatasıdır. Exception'ın alt sınıfı RuntimeException çoğunlukla programcı hatalarını temsil eder. Kontrol edilen ve kontrol edilmeyen istisna ayrımı bu farka dayanır.
Error ve Exception'ın ikisi de Throwable'dan türer; bu yüzden ikisi de fırlatılabilir ve teknik olarak yakalanabilir. Ama “ele alınmalı” olan, Exception ailesidir.
“catch (Exception e) her şeyi yakalar” sanmak. Exception, Error ailesini kapsamaz. Sonsuz özyinelemeden doğan StackOverflowError bu blokla yakalanmaz ve program izleme listesiyle sonlanır. Her şeyi yakalayan tip Throwable'dır; ama onu yakalamak da Error'ları gizlediği için önerilmez.
6.5Kontrol edilen ve kontrol edilmeyen istisnalar
6.5.1İki tür istisna
Java istisnaları derleyicinin tutumuna göre ikiye ayrılır.
Kontrol edilen istisna (checked exception), derleme zamanında derleyicinin denetlediği istisnadır. Programcı bu istisnayı ya ele almak ya da bildirmek zorundadır. Exception'dan türeyip RuntimeException'dan türemeyen sınıflar bu gruptadır. Kontrol edilmeyen istisna (unchecked exception) ise derleme zamanında denetlenmez ve çalışma zamanında ortaya çıkar. RuntimeException, Error ve bunların alt sınıfları bu gruptadır.
Kontrol edilen istisnalar, ortaya çıkabileceği önceden bilinen durumları anlatır. Dosya okuyan her program, dosyanın bulunamaması olasılığıyla yüzleşmelidir. Derleyici bu olasılık için bir plan ister. Kontrol edilmeyen istisnalar ise çoğunlukla bir programlama hatasının belirtisidir. Derleyici her bölmede, her dizi erişiminde ve her metot çağrısında bir plan isteseydi kod okunmaz hâle gelirdi.
Exception'ı genişletirse kontrol edilen, RuntimeException'ı genişletirse kontrol edilmeyen olur.6.5.2Kontrol edilen istisna: yakala ya da bildir
Kontrol edilen istisnalar için Java'nın kuralı “yakala ya da bildir” (catch or declare) kuralıdır. İstisna fırlatabilecek kod ya bir try-catch içinde ele alınır ya da metodun imzasında throws ile bildirilir. Bu kural derleme zamanında denetlenir; uyulmazsa program derlenmez.
Kuralın gerekçesi basittir. Var olmayan bir dosyayı okumaya çalışan program ne kadar doğru yazılırsa yazılsın, dosya o sırada silinmiş olabilir. Bu bir programcı hatası değildir; dış dünyanın bir gerçeğidir. Derleyici, programcının bu olasılığı unutmamasını sağlar.
public void dosyaOku() {
FileReader okuyucu = new FileReader("veri.txt");
}Ana.java:5: error: unreported exception FileNotFoundException; must be caught or declared to be thrown
FileReader okuyucu = new FileReader("veri.txt");FileReader'ın yapıcısı, imzasında throws FileNotFoundException bildirir. dosyaOku metodu bu istisnayı ne yakalamış ne de bildirmiştir. Derleyici hatanın iki çözümünü iletinin kendisinde söyler: must be caught (yakalanmalı) ya da declared to be thrown (fırlatılacağı bildirilmeli). Bildirerek çözüm şöyledir:
public void dosyaOku() throws FileNotFoundException {
// Dosya okuma kodu
FileReader okuyucu = new FileReader("veri.txt");
}Artık metot derlenir. Ama sorun ortadan kalkmamış, metodu çağıran koda geçmiştir. dosyaOku()'yu çağıran her kod da aynı kurala uymak zorundadır.
6.5.3Kontrol edilen istisnayı ele almak
İkinci çözüm, istisnayı metodun içinde yakalamaktır.
public static void dosyaOku2() {
try {
FileReader okuyucu = new FileReader("veri.txt");
// Dosya okuma kodu
} catch (FileNotFoundException e) {
// Kontrol edilen istisna burada ele alınıyor
System.err.println("Dosya bulunamadı: " + e.getMessage());
// Gerekirse istisna tekrar fırlatılabilir:
// throw e;
}
}Dosya bulunamadı: veri.txt (The system cannot find the file specified)Bu sürümde metot istisnayı kendisi yakalar; imzasında throws yoktur. Çağıran kodun hiçbir şey yapması gerekmez. Mesaj System.err'e, yani hata çıktısı akışına yazılır. Mesajın ikinci bölümü işletim sisteminden gelir. Yukarıdaki çıktı Windows'ta alınmıştır: (The system cannot find the file specified). Linux'ta aynı durumda (No such file or directory) yazılır.
Yorum satırındaki throw e; başka bir olasılığı gösterir. Yakalanan istisna, örneğin bir kayıt tutulduktan sonra, yeniden fırlatılabilir. O zaman istisna yine metottan dışarı çıkar ve metot imzasında yine throws FileNotFoundException bildirilmelidir.
Fırlatılmayan bir kontrol edilen istisnayı yakalamaya çalışmak. Dosya işlemi içermeyen bir try'a catch (FileNotFoundException e) yazılırsa derleyici exception FileNotFoundException is never thrown in body of corresponding try statement hatası verir. Derleyici kontrol edilen istisnaların nereden gelebileceğini bilir. catch (Exception e) ise bu hatayı vermez, çünkü Exception kontrol edilmeyen RuntimeException'ları da kapsar.
6.5.4Kontrol edilmeyen istisna
Kontrol edilmeyen istisnaların ele alınması ya da bildirilmesi zorunlu değildir. Bunlar genellikle mantık hatalarının ya da programcı hatalarının sonucudur: sıfıra bölme, null kullanımı, dizi sınırını aşma. throws ile belirtilmeleri ya da try-catch içine alınmaları gerekmez; derleyici sessiz kalır.
public void bolmeIslemi(int pay, int payda) {
// Kontrol edilmeyen istisna: ArithmeticException
int sonuc = pay / payda;
}Bu metot derlenir ve throws gerektirmez. payda sıfır olursa çalışma zamanında ArithmeticException oluşur. Böyle bir hatanın doğru çözümü çoğu zaman yakalamak değil, önlemektir. Bölmeden önce payda, kullanmadan önce null denetlenir.
public void bolmeIslemi(int pay, int payda) {
if (payda == 0) { // önleme
System.out.println("Payda sıfır olamaz");
return;
}
int sonuc = pay / payda;
}Önleme sürümünde istisna hiç oluşmaz. Denetim, sorunu kaynağında yakalar ve programcının durumu nasıl ele alacağına kendisinin karar vermesini sağlar. Burada metot bir mesaj yazıp erken döner. Gerçek bir programda çoğu zaman anlamlı bir mesajla IllegalArgumentException fırlatmak daha doğrudur, çünkü çağıranın bir hata yaptığını açıkça bildirir.
6.5.5Üç ailenin karşılaştırması
| Kontrol edilen | Kontrol edilmeyen | Error | |
|---|---|---|---|
| Üst sınıf | Exception (RuntimeException hariç) | RuntimeException | Error |
| Derleyici denetler mi? | Evet: ele al ya da bildir | Hayır | Hayır |
| Tipik neden | Programın dışındaki koşullar (dosya, ağ, veri tabanı) | Programcı hatası (null, sınır, bölme) | JVM ya da sistem sorunu |
| Ne yapılmalı? | Yakalayıp kurtarmak ya da throws ile iletmek | Kodu düzeltmek, girdiyi denetlemek | Genellikle yakalamamak; programı düzgün sonlandırmak |
| Örnekler | IOException, SQLException, ClassNotFoundException | NullPointerException, ArithmeticException | OutOfMemoryError, StackOverflowError |
IOException, NumberFormatException, SQLException, NullPointerException, FileNotFoundException, ClassCastException ve StackOverflowError sınıflarını kontrol edilen ve kontrol edilmeyen olarak ayıralım.
Her sınıf için üst sınıf zincirine bakılır. Zincirde RuntimeException ya da Error varsa sınıf kontrol edilmeyendir. Kontrol edilen: IOException, SQLException, FileNotFoundException. Kontrol edilmeyen: NumberFormatException (IllegalArgumentException üzerinden), NullPointerException, ClassCastException ve bir Error olan StackOverflowError.
main içinde, try-catch ve throws olmadan yazılınca Integer.parseInt("5"), Thread.sleep(100), new ArrayList<Integer>(-1) ve Objects.requireNonNull(x) satırlarından hangisi derlenmez?
Thread.sleep(100). Bu metot kontrol edilen InterruptedException'ı fırlatabileceğini bildirir; derleyici unreported exception InterruptedException hatası verir. Diğerleri en fazla kontrol edilmeyen bir istisna fırlatır (NumberFormatException, IllegalArgumentException, NullPointerException). Derleyici bunları denetlemez. Üçüncü satır derlenir, ama çalışırken istisna verir.
6.6İstisna fırlatmak
6.6.1throw deyimi
Java çalışma zamanı beklenmedik bir durumla karşılaştığında bir istisna nesnesi oluşturur ve onu fırlatır. Amaç, durumu üst düzeydeki koda bildirmektir. Programcı da aynı işi throw anahtar kelimesiyle yapabilir.
throw deyimi, bir istisna nesnesini fırlatır: throw ifade;. İfadenin değeri bir Throwable nesnesi olmalıdır. Nesne çoğunlukla aynı satırda new ile oluşturulur ve yapıcısına bir mesaj verilir.
throw new Exception("Bu bir istisna durumu!");İstisna, normal program akışını değiştirir. throw'dan sonraki satırlar çalışmaz; denetim uygun bir catch'e geçer. Fırlatılan istisna, çağıran kodda bir try-catch ile yakalanabilir. Hiçbir yerde ele alınmazsa JVM programı sonlandırır ve neyin yanlış gittiğini, nerede olduğunu bildirir.
try {
throw new Exception("Bu bir istisna durumu!");
} catch (Exception e) {
System.out.println(e);
}java.lang.Exception: Bu bir istisna durumu!Fırlatılabilecek pek çok tür vardır: IOException, NullPointerException, IllegalArgumentException ve programcının kendi yazdığı özel sınıflar. Yukarıdaki örnekte println(e), nesnenin toString() sonucunu yazar: sınıfın tam adı ve yapıcıya verilen mesaj.
throw'dan sonra aynı bloğa kod yazmak. throw koşulsuz çalışıyorsa ondan sonraki satıra hiçbir zaman ulaşılamaz. Derleyici bunu fark eder ve unreachable statement hatası verir. throw çoğunlukla bir if'in içinde bulunur; o zaman sonraki satırlar koşul yanlışken çalışır.
6.6.2Durumu en iyi anlatan tip
İstisna tipi seçilirken durumu en iyi temsil eden sınıf seçilir. Geçersiz bir argüman için IllegalArgumentException, nesnenin o anki durumunda yapılamayacak bir işlem için IllegalStateException uygundur. Var olan sınıflardan hiçbiri uymuyorsa programa özel bir istisna sınıfı yazılır.
class CustomException extends Exception {
public CustomException(String mesaj) {
super(mesaj);
}
}
public static void kontrolEt(int deger)
throws CustomException {
if (deger < 0) {
// Belirli bir durumu kontrol et ve istisna fırlat
throw new CustomException("Değer negatif olamaz!");
}
// Değer pozitifse, normal işlem devam eder
System.out.println("Değer uygun: " + deger);
}
// main içinde
try {
kontrolEt(5);
kontrolEt(-3);
kontrolEt(8);
} catch (CustomException e) {
System.out.println("Yakalandı: " + e.getMessage());
}Değer uygun: 5
Yakalandı: Değer negatif olamaz!CustomException, Exception'ın alt sınıfıdır. RuntimeException'dan türemediği için kontrol edilen bir istisnadır. Bu yüzden kontrolEt metodunun imzasında throws CustomException şarttır. Bu bildirim kaldırılırsa derleyici unreported exception CustomException; must be caught or declared to be thrown hatası verir.
Çıktıda Değer uygun: 8 satırı yoktur. kontrolEt(-3) istisna fırlattığı anda try'ın kalanı atlanır; kontrolEt(8) hiç çağrılmaz.
6.6.3Kod izleme: throw
Aynı programı çağrı yığınıyla birlikte adım adım izleyelim. Çağrı yığını, çalışan metotların çerçevelerini tutan yığındır; her metot çağrısı yığına bir çerçeve ekler, metot bitince çerçeve kaldırılır.
static void kontrolEt(int deger) throws CustomException {
if (deger < 0) {
throw new CustomException("Değer negatif olamaz!");
}
System.out.println("Değer uygun: " + deger);
}
// main içinde
try {
kontrolEt(5);
kontrolEt(-3);
kontrolEt(8);
} catch (CustomException e) {
System.out.println("Yakalandı: " + e.getMessage());
}| Adım | Satır | Çağrı yığını | Değişkenler | Yeni çıktı |
|---|---|---|---|---|
| 1 | 9: kontrolEt(5); | main() | — | — |
| 2 | 2: if (deger < 0) { | main() → kontrolEt(5) | deger=5 | — |
| 3 | 5: System.out.println("Değer uygun: " + deger); | main() → kontrolEt(5) | deger=5 | Değer uygun: 5 |
| 4 | 10: kontrolEt(-3); | main() | — | — |
| 5 | 2: if (deger < 0) { | main() → kontrolEt(-3) | deger=-3 | — |
| 6 | 3: throw new CustomException("Değer negatif olamaz!"); | main() → kontrolEt(-3) | deger=-3 | — |
| 7 | 12: } catch (CustomException e) { | main() | e=CustomException | — |
| 8 | 13: System.out.println("Yakalandı: " + e.getMessage()); | main() | e=CustomException | Yakalandı: Değer negatif olamaz! |
| 9 | 14: } | main() | — | — |
1. adımda main, try içinde kontrolEt(5)'i çağırır ve yığına yeni bir çerçeve eklenir. 5 < 0 yanlıştır; throw satırı atlanır. Değer uygun: 5 yazılır ve metot normal biçimde döner. 4. adımda sıradaki çağrı kontrolEt(-3)'tür. Bu kez koşul doğrudur.
kontrolEt(-3) çerçevesinde bir CustomException nesnesi oluştu ve fırlatıldı. Metotta catch olmadığı için bu çerçeve atılacak.6. adımda new ile istisna nesnesi oluşur ve throw onu fırlatır. Metodun 5. satırı çalışmaz. kontrolEt'te catch yoktur; bu yüzden çerçeve atılır ve istisna main'e geçer. 7. adımda try'ın kalanı, yani kontrolEt(8) atlanır ve uygun catch bulunur. 8. adımda getMessage(), yapıcıya verilen mesajı döndürür: Yakalandı: Değer negatif olamaz!. Son adımda catch biter ve program normal akışına döner.
6.6.4throw ve throws farkı
İki anahtar kelime yalnız bir harfle ayrılır, ama görevleri tamamen farklıdır.
throw | throws | |
|---|---|---|
| Görevi | Bir istisnayı fırlatır (şimdi) | Metodun fırlatabileceği istisnaları bildirir (ileride olabilir) |
| Yeri | Metot gövdesinde, bir deyim olarak | Metot imzasında, parametrelerden sonra |
| Ardından | Tek bir nesne: throw new X("…"); | Bir ya da daha çok tip: throws X, Y |
| Örnek | throw new ArithmeticException("Sıfıra bölme"); | void bol(int x, int y) throws ArithmeticException |
throw bir eylemdir; throws ise bir uyarı etiketidir. Bir metot throws ile bildirdiği istisnayı hiç fırlatmayabilir; bildirim yalnız bir olasılığı duyurur. Kontrol edilmeyen istisnaların throws ile bildirilmesi zorunlu değildir, ama belgeleme amacıyla yazılabilir. Tablodaki throws ArithmeticException bu türden bir bildirimdir.
İki kelimeyi karıştırmak iki tipik derleme hatasına yol açar. Birincisinde throws gövdede kullanılmıştır:
if (x < 0) {
throws new IllegalArgumentException("negatif");
}Ana.java:6: error: illegal start of expression
throws new IllegalArgumentException("negatif");Gövdede throws diye bir deyim yoktur; derleyici satırı bir ifadenin başlangıcı olarak tanıyamaz. İkincisinde kontrol edilen bir istisna bildirilmeden fırlatılmıştır:
static void f(int x) {
if (x < 0) throw new Exception("negatif");
}Ana.java:5: error: unreported exception Exception; must be caught or declared to be thrown
if (x < 0) throw new Exception("negatif");Exception, RuntimeException olmadığı için kontrol edilen bir tiptir. İki çözüm vardır: imzaya throws Exception eklemek ya da IllegalArgumentException gibi kontrol edilmeyen ve durumu daha iyi anlatan bir tip fırlatmak. Negatif argüman bir programcı hatası olduğu için ikinci çözüm daha uygundur.
“throw yalnız catch bloğunda kullanılabilir” sanmak. throw gövdenin her yerinde geçerli bir deyimdir: if içinde, döngüde, catch'te, hatta metodun ilk satırında. catch içinde kullanıldığında yakalanan istisnayı yeniden fırlatır.
6.6.5İstisna fırlatan metotları çağırmak
Bir metot çağrılırken imzasında bildirilen istisnalar dikkate alınır ve uygun bir strateji seçilir. Sunumdaki dosya okuma metodu kontrol edilen bir istisna bildirir.
public void dosyaOku() throws FileNotFoundException {
// Dosya okuma kodu
}Birinci strateji, çağıranın istisnayı ele almasıdır. Çağrı uygun bir try-catch bloğunun içine yazılır. Çoğu durumda tercih edilen yol budur.
try {
dosyaOku();
} catch (FileNotFoundException e) {
// Dosya bulunamadı durumuyla başa çıkma kodu
System.err.println("Dosya bulunamadı: " + e.getMessage());
}İkinci strateji, çağıranın da istisnayı bildirmesidir. Çağıran metot imzasında throws ile aynı tipi bildirir ve sorun bir üst düzeye geçer.
public void raporHazirla() throws FileNotFoundException {
dosyaOku();
}İkinci yol sorunu yalnız ertelediği için yakalamaktan daha az tercih edilir. Yine de bazen doğru seçimdir: raporHazirla kullanıcıyla konuşmuyorsa dosyanın bulunamaması durumunda ne yapılacağına da karar veremez. Bildirimdeki tip, fırlatılan tipin kendisi ya da bir üst sınıfı olabilir. throws IOException yazmak da derlenir, çünkü her FileNotFoundException bir IOException'dır. Hiçbir strateji seçilmezse derleyici dosyaOku(); satırı için unreported exception FileNotFoundException hatası verir.
main metodu bile throws yazabilir: public static void main(String[] args) throws IOException. O zaman istisna JVM'e kadar iletilir ve yakalanmayan istisna programı sonlandırır. Küçük deneme programlarında pratik bir kısayoldur; gerçek uygulamalarda kullanıcıya anlaşılır bir mesaj vermek daha doğrudur.
6.6.6Kod izleme: istisnanın yayılımı
İstisna yayılımı (exception propagation), yakalanmayan bir istisnanın çağrı yığınında çağıran metoda doğru ilerlemesidir. İstisnanın geçtiği her metot o anda sona erer ve çerçevesi yığından atılır.
static int bol(int a, int b) {
System.out.println("bol başladı");
int s = a / b;
System.out.println("bol bitti");
return s;
}
static int hesapla(int x) {
System.out.println("hesapla başladı");
int r = bol(x, 0);
System.out.println("hesapla bitti");
return r;
}
public static void main(String[] args) {
try {
int sonuc = hesapla(10);
System.out.println("Sonuç: " + sonuc);
} catch (ArithmeticException e) {
System.out.println("main: " + e.getMessage());
}
System.out.println("Program bitti");
}hesapla başladı
bol başladı
main: / by zero
Program bitti| Adım | Satır | Çağrı yığını | Değişkenler | Yeni çıktı |
|---|---|---|---|---|
| 1 | 15: int sonuc = hesapla(10); | main() | — | — |
| 2 | 8: System.out.println("hesapla başladı"); | main() → hesapla(10) | x=10 | hesapla başladı |
| 3 | 9: int r = bol(x, 0); | main() → hesapla(10) | x=10 | — |
| 4 | 2: System.out.println("bol başladı"); | main() → hesapla(10) → bol(10, 0) | a=10, b=0 | bol başladı |
| 5 | 3: int s = a / b; | main() → hesapla(10) → bol(10, 0) | a=10, b=0 | — |
| 6 | 9: int r = bol(x, 0); | main() → hesapla(10) | x=10 | — |
| 7 | 15: int sonuc = hesapla(10); | main() | — | — |
| 8 | 17: } catch (ArithmeticException e) {18: System.out.println("main: " + e.getMessage()); | main() | e=ArithmeticException | main: / by zero |
| 9 | 20: System.out.println("Program bitti"); | main() | — | Program bitti |
İlk dört adımda yığın büyür: main, hesapla(10)'u; hesapla da bol(10, 0)'ı çağırır. Yığının tepesinde artık bol vardır. 5. adımda 10 / 0 hesaplanır ve istisna bol'da doğar. bol'da try yoktur; bu yüzden metodun kalanı çalışmaz ve çerçevesi atılır. bol bitti yazılmaz.
bol'un çerçevesi atıldı (üstü çizili). İstisna, çağıran metoda, hesapla'ya geçti. Burada da try olmadığı için bu çerçeve de atılacak.6. adımda istisna çağıran yere, hesapla'ya geçer. Burada da try yoktur. hesapla bitti satırı atlanır ve hesapla'nın çerçevesi de atılır. 7. adımda istisna main'e ulaşır. Çağrı bir try içindedir; JVM uygun bir catch arar. Sonuç satırı atlanır. 8. adımda catch eşleşir; istisna yakalanmış ve yayılım durmuştur. Son adımda program normal biçimde biter. Kimse yakalamasaydı istisna JVM'e ulaşır ve program sonlanırdı.
Bu izlemenin ana fikri şudur: istisnanın doğduğu yerle yakalandığı yer farklı metotlar olabilir. bol ve hesapla sorunu bilmez, ama ona karışmaz da. Sorunla ilgilenen tek metot main'dir. Her metot yalnız kendi işini yapar; hata işleme kodu tek bir yerde toplanır.
c(x), c x yazar; x > 2 ise IllegalArgumentException("x=" + x) fırlatır, değilse c bitti yazar. b(x), bir try içinde c(x)'i, c(x + 2)'yi çağırır ve b bitti yazar; finally'de b finally yazar. main, try içinde b(1)'i çağırır ve main devam yazar; catch main: ile mesajı yazar; en sonda son yazılır. Çıktı nedir?
c 1, c bitti, c 3, b finally, main: x=3, son. İlk çağrıda 1 > 2 yanlıştır ve c normal biter. c(3)'te istisna doğar; c bitti yazılmaz. b'de catch yoktur ama finally vardır: istisna yoluna devam etmeden önce b finally yazılır. b bitti ve main devam atlanır.
6.6.7JVM istisnayı nasıl ele alır?
Yayılım izlemesinde görülen süreç her istisnada aynı adımlarla işler:
- JVM ya da bir
throwdeyimi bir istisna nesnesi oluşturur. Nesne tipi, mesajı ve oluştuğu yerin izleme listesini içerir. - İstisnanın oluştuğu metotta, onu saran bir
tryve uyumlu bircatcharanır. - Bulunamazsa metot o anda sona erer; varsa
finallybloğu çalışır. İstisna çağıran metoda geçer ve arama orada sürer. - Uygun bir
catchbulunursa o çalışır; programcatch'ten sonraki satırdan devam eder. mainde yakalamazsa JVM'in varsayılan işleyicisi izleme listesini yazdırır ve programı, daha kesin söylemek gerekirse o iş parçacığını sonlandırır.
Uygun catch'i bulmak için çağrı yığınında aşağı doğru ilerleyen ve yol üzerindeki çerçeveleri atan bu sürece yığın çözülmesi (stack unwinding) denir.
main → a → b → c. İstisna c'de doğar ve ters yönde, catch bulana kadar ilerler.Yakalanan istisna nesnesi sıradan bir nesnedir. catch bloğu bittikten sonra ona başka bir referans tutulmuyorsa nesne artık kullanılmaz. Çöp toplayıcı (garbage collector) onu bir süre sonra bellekten temizler.
6.7Çalışma zamanı istisnaları
6.7.1RuntimeException ailesi
RuntimeException ve alt sınıfları, programcı hatası ya da mantık hatası sonucu oluşan istisnaları temsil eder. Derleme zamanında denetlenmezler ve ele alınmaları zorunlu değildir. Ama bu, onları görmezden gelmek anlamına gelmez. Programcı bu durumları bilinçli olarak önler ya da ele alır.
| Sınıf | Ne zaman oluşur? | Örnek |
|---|---|---|
NullPointerException | null bir referansın üyesi kullanılınca | String s = null; s.length(); |
ArrayIndexOutOfBoundsException | Dizi sınırları dışında indis | int[] d = new int[3]; d[5] = 1; |
ArithmeticException | Tam sayıyı sıfıra bölme, kesin aritmetikte taşma | 10 / 0 |
IllegalArgumentException | Metoda geçersiz argüman verilince | new ArrayList<>(-1) |
NumberFormatException | Metin geçerli bir sayı değilken dönüştürme | Integer.parseInt("abc") |
ClassCastException | Nesne, dönüştürülmek istenen tipte değilken | (Integer) nesne |
Sunumdaki “satır satır” örneklerde her satır tek başına denendiğinde ne olduğu verilir. Gerçek bir programda ilk istisna akışı keser ve sonraki satırlar çalışmaz. Notta verilen bütün iletiler JDK 21 ile her satır ayrı bir try içinde çalıştırılarak alınmıştır.
6.7.2NullPointerException
null bir referans hiçbir nesneyi göstermez. Bu referans üzerinden metot çağırmak, bir alana erişmek ya da dizinin length değerini okumak NullPointerException'a (kısaca NPE) yol açar. Bu istisna en sık karşılaşılan hata kaynaklarındandır.
String str = null;
int length = str.length();Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.length()" because "str" is nullJava 14'ten önce bu ileti yalnız java.lang.NullPointerException idi ve hangi referansın null olduğunu söylemezdi. Aynı satırda birkaç referans kullanılıyorsa (a.b().c()) suçluyu bulmak zordu. Java 14'ten beri açıklayıcı ileti hangi işlemin neden yapılamadığını söyler. Değişken adı ise yalnız sınıf hata ayıklama bilgisiyle derlendiğinde görünür (javac -g; IDE'ler bunu varsayılan olarak yapar). Düz javac ile derlenen aynı program adı bilemez ve şunu yazar:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local1>" is nullNPE'yi önlemenin birkaç yolu vardır:
if (str != null)ile açık denetim.Objects.requireNonNull(x, mesaj):xnullise hemen, anlamlı bir mesajla NPE fırlatır.Objects.requireNonNullElse(x, varsayilan)(Java 9+):xnullise varsayılan değeri döndürür.Optionalsınıfı (Java 8+): “değer olmayabilir” durumunu bir tiple açıkça temsil eder.- Kütüphaneler: örneğin Apache Commons Lang'in
StringUtilssınıfınull'a dayanıklı metin işlemleri sunar.
Aşağıdaki kod bu yolları bir arada gösterir.
1 String str = null;
2 int length = str.length();
3 Objects.requireNonNull(str, "Str null olamaz");
4 Optional<String> optionalStr = Optional.ofNullable(str);
5 length = optionalStr.map(String::length).orElse(0);
6 String defaultValue = Objects.requireNonNullElse(str, "0");
7 if (str != null) {
8 System.out.println(str.length());
9 }
10 System.out.println("Ali".equals(str));| Satır | Sonuç |
|---|---|
| 2 | NullPointerException: Cannot invoke "String.length()" because "str" is null |
| 3 | NullPointerException: Str null olamaz |
| 5 | length = 0: değer yoktur, orElse(0) devreye girer |
| 6 | defaultValue = "0" |
| 7–9 | Koşul yanlış; hiçbir şey yazılmaz, istisna yoktur |
| 10 | false: sabit metin öndedir, null argüman sorun değildir |
3. satır ile 2. satır aynı istisnayı fırlatır; o hâlde requireNonNull ne kazandırır? İki şey. Birincisi, mesajı programcı belirler. İkincisi ve daha önemlisi, hata kaynağında yakalanır. requireNonNull metot başlarında kullanılır. Bir yapıcı, aldığı parametre null ise nesneyi hiç oluşturmaz. Böylece bozuk bir nesne programın içinde dolaşıp çok sonra, ilgisiz bir yerde NPE vermez.
5. satır Optional'ı kullanır. Optional.ofNullable(str), str null ise boş bir kutu, değilse içinde str olan bir kutu üretir. map(String::length) kutu doluysa içindekinin uzunluğunu alır, boşsa hiçbir şey yapmaz. orElse(0), kutu boşsa 0 verir. Sonuç bir NPE değil, 0'dır.
10. satır yaygın bir alışkanlığı gösterir. "Ali".equals(str) güvenlidir, çünkü metot sabit metin üzerinden çağrılır ve sabit metin hiçbir zaman null değildir. str.equals("Ali") ise str null olduğunda NPE fırlatır.
NPE'yi catch (NullPointerException e) ile yakalayıp geçmek. NPE neredeyse her zaman bir programlama hatasıdır: bir değişkene değer atanmamış ya da bir metot beklenmedik biçimde null döndürmüştür. Doğru çözüm null'ın nereden geldiğini bulup orayı düzeltmektir. Yakalamak yalnız belirtiyi gizler.
6.7.3Sınır aşımı istisnaları
Bir dizinin geçerli indisleri 0'dan length − 1'e kadardır. Negatif indis de geçersizdir. Bu sınırların dışındaki bir indise erişmek ArrayIndexOutOfBoundsException fırlatır.
Bu sınıf, IndexOutOfBoundsException ailesinin bir üyesidir. Sınır aşımında hangi sınıfın fırlatılacağı, erişilen yapıya bağlıdır:
- dizi (
int[]),VectorveArrays.asList(...)listesi →ArrayIndexOutOfBoundsException; ArrayList.get→IndexOutOfBoundsException(üst sınıfın kendisi);String.charAt→StringIndexOutOfBoundsException.
Hepsini birden yakalamak için ortak üst sınıf yazılır: catch (IndexOutOfBoundsException e).
1 int[] numbers = {1, 2, 3};
2 int value = numbers[5];
3 ArrayList<String> list = new ArrayList<>();
4 String svalue = list.get(5);
5 Vector<Integer> vector = new Vector<>();
6 value = vector.get(10);
7 String[] names = {"Alice", "Bob", "Charlie"};
8 svalue = Arrays.asList(names).get(5);
9 String str = "Hello";
10 char ch = str.charAt(10);| Satır | İstisna | Mesaj |
|---|---|---|
| 2 | ArrayIndexOutOfBoundsException | Index 5 out of bounds for length 3 |
| 4 | IndexOutOfBoundsException | Index 5 out of bounds for length 0 |
| 6 | ArrayIndexOutOfBoundsException | Array index out of range: 10 |
| 8 | ArrayIndexOutOfBoundsException | Index 5 out of bounds for length 3 |
| 10 | StringIndexOutOfBoundsException | Index 10 out of bounds for length 5 |
4. satır özellikle öğreticidir. Liste new ArrayList<>(20) gibi bir kapasiteyle oluşturulsa bile boyutu 0'dır, çünkü henüz hiç öğe eklenmemiştir. get metodu indisi kapasiteye göre değil, boyuta göre denetler. 6. satırdaki Vector'ün mesajı farklı biçimdedir, çünkü bu eski sınıf mesajını kendisi oluşturur. 8. satırdaki liste, names dizisinin üzerine kurulmuş bir görünümdür; erişim doğrudan diziye yapılır ve dizinin istisnası fırlatılır.
Sınır aşımının en sık nedeni döngü koşulundaki tek bir karakterdir. Doğru döngü length ile sınırlanır:
int[] numbers = {1, 2, 3};
for (int i = 0; i < numbers.length; i++) {
System.out.print(numbers[i] + " ");
}1 2 3Koşul i <= numbers.length yazılırsa döngü bir tur fazla döner. Son turda numbers[3]'e erişilir ve Index 3 out of bounds for length 3 iletili istisna oluşur. Dizideki her öğeyi dolaşmak için en güvenli yazım for-each döngüsüdür; orada indis hiç yazılmaz.
6.7.4ArithmeticException
ArithmeticException, matematiksel bir işlem sırasında oluşan hatayı temsil eder. En bilinen örneği tam sayıyı sıfıra bölmektir; hem / hem % işleci bu istisnayı fırlatır. Başka kaynakları da vardır:
Mathsınıfının “kesin” (exact) metotları taşmada fırlatır:addExact,multiplyExact,toIntExact…BigDecimalyüksek duyarlıklı aritmetik sağlar. Sıfıra bölmede ve sonucu tam gösterilemeyen bölmede (1/3) fırlatır.
Bazı işlemler ise beklenenin aksine istisna fırlatmaz:
doubleişlemleri istisna fırlatmaz:10.0 / 0→Infinity,Math.sqrt(-1)→NaN.Random.nextInt(0),ArithmeticExceptiondeğil,IllegalArgumentExceptionfırlatır: sınır pozitif olmalıdır.- Sıradan
inttoplaması taşınca istisna oluşmaz; değer başa sarar.Integer.MAX_VALUE + 1→-2147483648. Buna sessiz taşma denir. Taşmanın fark edilmesi gerekiyorsaMath.addExactkullanılır.
1 int result = 10 / 0;
2 double dresult = Math.sqrt(-1);
3 System.out.println(dresult);
4 System.out.println(10.0 / 0);
5 int t = Math.addExact(Integer.MAX_VALUE, 1);
6 Random random = new Random();
7 int randomNumber = random.nextInt(0);
8 BigDecimal num1 = new BigDecimal("10");
9 BigDecimal num2 = BigDecimal.ZERO;
10 BigDecimal bresult = num1.divide(num2);| Satır | Sonuç |
|---|---|
| 1 | ArithmeticException: / by zero |
| 3 | NaN yazar, istisna yoktur |
| 4 | Infinity yazar, istisna yoktur |
| 5 | ArithmeticException: integer overflow |
| 7 | IllegalArgumentException: bound must be positive |
| 10 | ArithmeticException: Division by zero |
Kayan noktalı sayılarda sıfıra bölme, IEEE 754 standardının kurallarına göre özel değerler üretir. Pozitif bir sayının sıfıra bölümü Infinity, negatif bir sayınınki -Infinity, 0.0 / 0 ise NaN'dır (Not a Number, sayı değil). Bu değerler sessizce hesaplara karışır ve sonraki her işlemi bozar. Bu yüzden double sonuçları gerektiğinde ayrıca denetlenir: Double.isNaN(x), Double.isInfinite(x).
BigDecimal için 1/3 örneği de çalıştırılarak denendi. BigDecimal.ONE.divide(new BigDecimal("3")) şu istisnayı verir: Non-terminating decimal expansion; no exact representable decimal result. Sonsuz ondalık açılım tam olarak saklanamaz. Çözüm, bölmeye bir ölçek ve yuvarlama kuralı vermektir.
10 / 0, 10 % 0, Math.toIntExact(3000000000L) ve 10.0 / 0 ifadelerinden hangisi çalışırken istisna fırlatmaz?
10.0 / 0. Kayan noktalı bölme IEEE 754 kurallarına uyar ve Infinity üretir. Tam sayı bölmesi ve kalan işlemi sıfırla ArithmeticException: / by zero verir. 3.000.000.000 değeri int aralığına (en çok 2.147.483.647) sığmadığı için toIntExact integer overflow fırlatır.
6.7.5IllegalArgumentException
IllegalArgumentException, bir metodun geçersiz bir argüman aldığını belirtir. Kontrol edilmeyen bir istisnadır. Bir metodun ilk işi argümanlarını denetleyip gerekiyorsa bu istisnayı fırlatmaktır. Böylece hata kaynağında durdurulur.
public void yasAyarla(int age) {
if (age < 0) {
throw new IllegalArgumentException("Yaş negatif olamaz");
}
this.age = age;
}
public void adAyarla(String name) {
if (name.length() < 3) {
throw new IllegalArgumentException("3 karakter olmalı");
}
this.name = name;
}Java kütüphanesi de bu istisnayı yaygın biçimde kullanır:
Arrays.copyOfRange(d, 5, 2): başlangıç bitişten büyük;new ArrayList<>(-1): negatif kapasite;Thread'insetPriority(11)metodu: öncelik 1 ile 10 arasında olmalı;new File(uri): adresfile:biçiminde değil.
1 yasAyarla(-5);
2 adAyarla("Al");
3 int[] numbers = {1, 2, 3};
4 Arrays.copyOfRange(numbers, 5, 2);
5 List<Integer> liste = new ArrayList<>(-1);
6 File file = new File(URI.create("http://ornek.com/a.txt"));
7 Thread thread = new Thread(gorev);
8 thread.setPriority(11);| Satır | Fırlatılan istisna |
|---|---|
| 1 | IllegalArgumentException: Yaş negatif olamaz |
| 2 | IllegalArgumentException: 3 karakter olmalı |
| 4 | IllegalArgumentException: 5 > 2 |
| 5 | IllegalArgumentException: Illegal Capacity: -1 |
| 6 | IllegalArgumentException: URI scheme is not "file" |
| 8 | IllegalArgumentException (mesajsız) |
Her geçersiz durum bu istisnayı vermez. new FileReader("invalid/file/path"), kontrol edilen FileNotFoundException fırlatır. new Thread(null) ise hiç fırlatmaz: null görev, “yapılacak iş yok” anlamına gelir ve iş parçacığı başlatılınca hemen biter. IllegalArgumentException'ın en tanınmış alt sınıfı NumberFormatException'dır.
İyi bir mesaj, geçersiz değeri ve beklenen aralığı söyler: "Yaş negatif olamaz: " + age. 8. satırdaki mesajsız istisna hata ayıklamayı zorlaştırır. İletiyi okuyan programcı, önceliğin kaç verildiğini ve kaç olması gerektiğini bilemez.
Argüman denetiminde null'ı unutmak. adAyarla(null) çağrısında name.length() denetimin kendisi NPE fırlatır; programcının yazdığı anlamlı mesaj hiç görünmez. Denetim sırası önce null, sonra değerdir: Objects.requireNonNull(name, "ad null olamaz"), ardından uzunluk denetimi.
6.7.6NumberFormatException ve güvenli dönüştürme
Bir metin sayıya dönüştürülürken metin geçerli bir sayıyı temsil etmiyorsa NumberFormatException oluşur. Integer.parseInt, Double.parseDouble, Float.parseFloat, Short.parseShort, Long.parseLong, Byte.parseByte ve bunların valueOf sürümleri bu istisnayı fırlatabilir. Değer tipin aralığı dışındaysa da aynı istisna oluşur: Byte.parseByte("300") şu iletiyi verir: Value out of range. Value:"300" Radix:10.
String strNumber = "abc";
int number = Integer.parseInt(strNumber);
strNumber = "123abc";
number = Integer.parseInt(strNumber);
String strDouble = "12.34abc";
double doubleValue = Double.parseDouble(strDouble);
String strShort = "45xyz";
short shortValue = Short.parseShort(strShort);| Çağrı | Mesaj |
|---|---|
Integer.parseInt("abc") | For input string: "abc" |
Integer.parseInt("123abc") | For input string: "123abc" |
Double.parseDouble("12.34abc") | For input string: "12.34abc" |
Short.parseShort("45xyz") | For input string: "45xyz" |
"123abc" örneği dönüştürmenin “her şey ya da hiç” kuralını gösterir. Metnin başındaki 123 geçerli olsa da metin bir bütün olarak sayı değildir. parseInt yarım sonuç döndürmez. NumberFormatException, IllegalArgumentException'ın alt sınıfıdır. Kullanıcı girdisi her zaman bu riski taşır: girdi ya önceden denetlenir ya da istisna yakalanır.
Dönüştürme bir yardımcı metotta, tek bir yerde denetlenebilir. Geçersiz girdi için metot bir varsayılan değer döndürür.
static int guvenliOku(String metin, int varsayilan) {
try {
return Integer.parseInt(metin.trim());
} catch (NumberFormatException e) {
return varsayilan;
}
}
// main içinde
System.out.println(guvenliOku(" 42 ", 0));
System.out.println(guvenliOku("kırk", 0));
System.out.println(guvenliOku("10.5", -1));42
0
-1trim(), baştaki ve sondaki boşlukları siler. Bu sayede " 42 " girdisi 42'ye dönüşür. parseInt(" 42 ") tek başına istisna fırlatırdı, çünkü boşluk bir rakam değildir. "kırk" bir sayı değildir; varsayılan 0 döner. "10.5" de bir int değildir; ondalık sayı için Double.parseDouble kullanılır.
Sunumdaki adım adım izleme aynı metodu üç yeni girdiyle çalıştırır.
static int guvenliOku(String metin, int varsayilan) {
try {
return Integer.parseInt(metin.trim());
} catch (NumberFormatException e) {
return varsayilan;
}
}
System.out.println(guvenliOku(" 7 ", 0));
System.out.println(guvenliOku("yedi", 0));
System.out.println(guvenliOku("7.5", -1));7
0
-1| Adım | Satır | Değişkenler | Yeni çıktı |
|---|---|---|---|
| 1 | 8: System.out.println(guvenliOku(" 7 ", 0)); | — | — |
| 2 | 3: return Integer.parseInt(metin.trim()); | metin=" 7 ", varsayilan=0 | 7 |
| 3 | 9: System.out.println(guvenliOku("yedi", 0)); | — | — |
| 4 | 3: return Integer.parseInt(metin.trim()); | metin="yedi", varsayilan=0 | — |
| 5 | 4: } catch (NumberFormatException e) {5: return varsayilan; | metin="yedi", varsayilan=0, e=NumberFormatException | 0 |
| 6 | 10: System.out.println(guvenliOku("7.5", -1)); | — | — |
| 7 | 3: return Integer.parseInt(metin.trim()); | metin="7.5", varsayilan=-1 | — |
| 8 | 4: } catch (NumberFormatException e) {5: return varsayilan; | metin="7.5", varsayilan=-1, e=NumberFormatException | -1 |
İlk çağrıda metin " 7 "'dir ve başta ve sonda birer boşluk vardır. trim() boşlukları atar, "7" sayıya çevrilir ve try'ın içinden 7 döner. İkinci çağrıda metin "yedi"'dir. parseInt rakam olmayan metinde istisna fırlatır ve return deyimi tamamlanamaz.
trim() sonucu yine yedi'dir; dönüşüm başarısız olur ve catch bloğu varsayılan değeri, 0'ı döndürür.catch eşleşir ve varsayılan değer 0 döner. İstisna metodun dışına çıkmaz. Üçüncü çağrıda metin "7.5", varsayılan değer −1'dir. Nokta bir rakam değildir; "7.5" geçerli bir int değildir. −1 döner. Çağıran kod, istisna yerine varsayılan değerle yoluna devam eder.
Bu yaklaşımın bir bedeli vardır. Varsayılan değer, geçerli bir sonuçla karışmayacak biçimde seçilmelidir. guvenliOku("0", -1) 0 döndürür; guvenliOku("-1", 0) ise −1 döndürür. Varsayılan değer geçerli girdilerden ayırt edilemiyorsa istisnayı çağırana iletmek daha doğrudur. Bu, dönüş kodu tartışmasının küçük bir tekrarıdır.
6.7.7ClassCastException ve instanceof
Bir nesne, dönüştürülmek istendiği tipten değilken açık dönüşüm (cast) yapılırsa ClassCastException oluşur. Belirleyici olan değişkenin tipi değil, nesnenin gerçek tipidir. Object obj = "Merhaba"; satırından sonra obj'nin tipi Object'tir, ama gösterdiği nesne bir String'dir.
Ham (raw) tipli koleksiyonlar, yani tip parametresi yazılmamış ArrayList, Map ve Set, her türden nesneyi kabul eder. Yanlış tipte bir öğe alınıp dönüştürülürken dönüşüm başarısız olur. Sıralı koleksiyonlar (TreeSet, TreeMap) ise birbiriyle karşılaştırılamayan tipte öğe eklenirken de bu istisnayı fırlatır.
1 Object obj = "Merhaba";
2 Integer number = (Integer) obj;
3 ArrayList list = new ArrayList();
4 list.add("Merhaba");
5 number = (Integer) list.get(0);
6 Map map = new HashMap();
7 map.put("anahtar", "Değer");
8 Integer value = (Integer) map.get("anahtar");
9 Set set = new HashSet();
10 set.add("Merhaba");
11 value = (Integer) set.iterator().next();
12 Object[] objArray = new String[5];
13 Integer[] intArray = (Integer[]) objArray;2, 5, 8, 11 ve 13. satırların her biri ClassCastException fırlatır. Örneğin 2. satırın iletisi şudur:
class java.lang.String cannot be cast to class
java.lang.IntegerJDK 21'in tam iletisi bir parantez daha ekler: iki sınıfın da java.base modülünde ve aynı yükleyicide olduğunu söyler. 13. satır dizi tipleri arasındaki dönüşümdür. objArray gerçekte bir String[]'dir ve Integer[]'e dönüştürülemez; iletide diziler [Ljava.lang.String; biçiminde görünür.
Dönüşümden önce instanceof ile nesnenin tipi sınanabilir:
Object obj = "Merhaba";
if (obj instanceof Integer) {
Integer sayi = (Integer) obj;
System.out.println("Sayı: " + sayi);
} else {
System.out.println("obj bir Integer değil");
}obj bir Integer değilJava 16'dan beri örüntü eşleme (pattern matching) sınama ile dönüşümü birleştirir. Koşul doğruysa sayi değişkeni zaten Integer tipindedir; ayrı bir dönüşüm satırı gerekmez.
if (obj instanceof Integer sayi) {
System.out.println("Sayı: " + sayi);
}Daha iyi çözüm, sorunu derleme zamanına taşımaktır. Jenerikler (generics) bir koleksiyonun hangi tipte öğe tuttuğunu derleyiciye söyler.
ArrayList<String> liste = new ArrayList<>();
liste.add("Merhaba");
Integer n = (Integer) liste.get(0);Ana.java:8: error: incompatible types: String cannot be converted to Integer
Integer n = (Integer) liste.get(0);Çalışma zamanı istisnası yerine bir derleme hatası alınır. Hata daha erken, program hiç çalıştırılmadan bulunur. Bu yüzden ham tipler yerine jenerik tipler tercih edilir.
6.8Kontrol edilen istisna sınıfları
6.8.1Sık karşılaşılan sınıflar
Kontrol edilen istisnalar, kodun hatayı ele almasını ve uygun biçimde kurtarmasını gerektiren durumları temsil eder. Bu durumlar çoğunlukla programın dışındaki koşullardan doğar: dosya sistemi, ağ, veri tabanı, başka bir iş parçacığı. Derleyici bu istisnaların ele alınmasını şart koşar.
| Sınıf | Paket | Ne zaman? |
|---|---|---|
FileNotFoundException | java.io | Açılmak istenen dosya yok ya da açılamıyor |
IOException | java.io | Giriş/çıkış işlemlerinde genel hata (üst sınıf) |
SQLException | java.sql | Veri tabanı erişiminde hata |
ParseException | java.text | Metni bir biçime göre ayrıştırırken hata |
ClassNotFoundException | java.lang | Adıyla yüklenmek istenen sınıf bulunamadı |
InterruptedException | java.lang | Bekleyen iş parçacığı kesildi (Thread.sleep) |
NotSerializableException | java.io | Serileştirilemeyen bir nesne yazılmak istendi |
Tablodaki sınıfların hepsi Exception'dan türer ve zincirlerinde RuntimeException yoktur. ClassNotFoundException araya bir düzey daha alır (ReflectiveOperationException); NotSerializableException ise ObjectStreamException üzerinden IOException'a bağlanır.
6.8.2FileNotFoundException ve IOException
FileNotFoundException, bir dosya açılmak istendiğinde dosya bulunamazsa oluşur. Aynı istisna, verilen yol bir klasörse ya da dosyayı okuma izni yoksa da fırlatılır.
try {
FileReader fileReader = new FileReader("dosya.txt");
} catch (FileNotFoundException e) {
// Dosya bulunamadı durumuyla ilgili işlemler
System.out.println(e);
}java.io.FileNotFoundException: dosya.txt
(The system cannot find the file specified)Bu sınıf IOException'ın alt sınıfıdır; catch (IOException e) onu da yakalar. Göreli bir yol ("dosya.txt"), programın çalışma klasörüne göre aranır. Geliştirme ortamlarında çalışma klasörü genellikle proje klasörüdür, kaynak kodun bulunduğu klasör değil. Dosya “yanlış klasörde” olabilir. Bu istisnaya tipik tepki, kullanıcıya durumu bildirip yeni bir ad istemek ya da varsayılan değerlerle devam etmektir.
Aynı istisnanın klasör için verdiği ileti de denendi: new FileReader(".") Windows'ta java.io.FileNotFoundException: . (Access is denied) verir. Sınıfın adı “dosya bulunamadı” dese de istisna “dosya açılamadı” anlamında kullanılır.
IOException, giriş/çıkış işlemlerinde oluşan genel hatayı temsil eder ve FileNotFoundException gibi birçok alt sınıfı vardır. Aşağıdaki kod bir metin dosyasını satır satır okur.
try {
BufferedReader reader =
new BufferedReader(new FileReader(path));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
reader.close();
} catch (IOException e) {
// IOException durumuyla ilgili işlemler
e.printStackTrace();
}birinci satır
ikinci satırKod çalışır, ama bir kusuru vardır. readLine() bir istisna fırlatırsa denetim doğrudan catch'e geçer ve reader.close() satırı hiç çalışmaz. Dosya açık kalır: bu bir kaynak sızıntısıdır. Çözüm try-with-resources'tır:
try (BufferedReader okuyucu =
new BufferedReader(new FileReader(path))) {
String satir;
while ((satir = okuyucu.readLine()) != null) {
System.out.println(satir);
}
} catch (IOException e) {
System.out.println(e.getMessage());
}Bu sürümde okuyucu, blok nasıl biterse bitsin kapatılır. Dosya yoksa new FileReader(path) istisna fırlatır; o durumda kapatılacak bir kaynak da oluşmamıştır ve catch doğrudan çalışır. catch (IOException e) hem açılma hatasını (FileNotFoundException) hem de okuma hatalarını yakalar. Var olmayan yok.txt ile denendiğinde blok, yok.txt (The system cannot find the file specified) mesajını yazar.
6.8.3SQLException
SQLException, veri tabanı işlemleri sırasında bir hata oluştuğunda fırlatılır. Bağlantı kurulamayabilir, SQL söz dizimi yanlış olabilir, tablo bulunmayabilir ya da veri tabanı sürücüsü yüklenmemiş olabilir. İstisna nesnesi ayrıntı veren üç metot sunar: getMessage(), getSQLState() ve getErrorCode().
try {
Connection connection =
DriverManager.getConnection("jdbc:mysql://localhost/okul");
PreparedStatement preparedStatement =
connection.prepareStatement("SELECT user_id FROM users");
// Sorguyu çalıştırma ve sonuçları alıp işleme
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
// Sonuçları işleme
System.out.println("User ID: " + resultSet.getInt("user_id"));
}
// Bağlantıyı kapatma
connection.close();
} catch (SQLException e) {
// SQLException durumuyla ilgili işlemler
System.out.println(e);
}java.sql.SQLException: No suitable driver
found for jdbc:mysql://localhost/okulBu program MySQL sürücüsü sınıf yolunda yokken çalıştırılmıştır. DriverManager, jdbc:mysql: ile başlayan adresi tanıyan bir sürücü bulamaz. Bu durumda getSQLState() 08001 değerini, getErrorCode() ise 0 döndürür. 08 ile başlayan durum kodları bağlantı hatalarını anlatan standart kodlardır.
Koddaki connection.close() çağrısı, IOException örneğindeki kusuru taşır: sorgu sırasında bir istisna oluşursa bağlantı açık kalır. Bağlantı da bir kaynaktır ve try-with-resources ile kapatılır. Connection, PreparedStatement ve ResultSet üçü de AutoCloseable'dır ve aynı parantezde açılabilir.
6.8.4ParseException ve ClassNotFoundException
ParseException (java.text paketi), bir metin belirli bir biçime göre ayrıştırılırken uyuşmazlık olursa fırlatılır.
try {
SimpleDateFormat form =
new SimpleDateFormat("dd/MM/yy");
Date date = form.parse("01-01-2022");
} catch (ParseException e) {
// Ayrıştırma hatasıyla ilgili işlemler
System.out.println(e);
}java.text.ParseException: Unparseable date: "01-01-2022"Biçim dd/MM/yy'dir; gün ile ay arasında / beklenir. Metinde ise - vardır. Ayrıştırıcı ilk iki karakteri gün olarak okur ve üçüncü karakterde takılır. e.getErrorOffset() bu konumu verir: 2, yani üçüncü karakter.
ClassNotFoundException, bir sınıf adıyla yüklenmek istendiğinde (Class.forName) sınıf bulunamazsa fırlatılır.
try {
Class.forName("java.util.Scanner");
System.out.println("Scanner yüklendi");
// Var olmayan bir sınıfı yüklemeye çalışma
Class.forName("com.example.NonExistentClass");
} catch (ClassNotFoundException e) {
System.out.println(e);
}Scanner yüklendi
java.lang.ClassNotFoundException:
com.example.NonExistentClassjava.util.Scanner standart kütüphanededir ve sorunsuz yüklenir. com.example.NonExistentClass ise sınıf yolunda yoktur. Sınıfı adıyla yüklemek, veri tabanı sürücüleri gibi derleme sırasında bilinmeyen sınıflarla çalışırken kullanılır. Ad bir metin olduğu için derleyici onu denetleyemez; sınıfın bulunup bulunmayacağı ancak çalışırken belli olur.
Bu istisna, Error ailesindeki NoClassDefFoundError ile karıştırılmamalıdır. ClassNotFoundException'da program bir sınıfı adıyla kendisi ister. NoClassDefFoundError'da ise derleme sırasında var olan bir sınıf, program çalışırken bulunamamıştır; bu bir kurulum sorunudur.
6.8.5NotSerializableException
Serileştirme (serialization), bir nesnenin bayt akışına dönüştürülmesidir. Bu akış bir dosyaya yazılabilir ya da ağ üzerinden gönderilebilir. Daha sonra akış yeniden nesneye dönüştürülür.
Java'da bir sınıfın nesnelerinin serileştirilebilmesi için sınıfın Serializable arayüzünü uygulaması gerekir. Bu arayüzün hiçbir metodu yoktur; yalnız bir izin işaretidir. Arayüzü uygulamayan bir sınıfın nesnesi yazılmak istenirse NotSerializableException oluşur.
class Nokta { // Serializable değil
int x = 3, y = 4;
}
// main içinde
try (ObjectOutputStream outputStream =
new ObjectOutputStream(new ByteArrayOutputStream())) {
// Nesneyi ObjectOutputStream ile serileştirmeye çalışma
Nokta notSerializableObj = new Nokta();
outputStream.writeObject(notSerializableObj);
} catch (NotSerializableException e) {
System.out.println(e);
} catch (IOException e) {
System.out.println("Diğer G/Ç hatası: " + e);
}java.io.NotSerializableException: NoktaNotSerializableException, IOException'ın dolaylı bir alt sınıfıdır. Bu yüzden onun catch'i IOException'ınkinden önce yazılır. Sıra ters olsaydı derleyici has already been caught hatası verirdi. Çözüm, sınıfa arayüzü eklemektir:
class Nokta implements Serializable {
int x = 3, y = 4;
}Bu düzeltmeyle nesne yazılır ve geri okunabilir. Deneme programı nesneyi bir bayt dizisine yazıp yeniden okudu ve x = 3, y = 4 değerlerini geri aldı.
6.9Error sınıfları
Error sınıfları, programın normal çalışmasını temelden etkileyen, sistem düzeyindeki sorunları temsil eder. Kontrol edilmeyen tiplerdir. Genellikle uygulama kodunun bir satırından değil, JVM'in ve çalışma ortamının durumundan kaynaklanırlar. JVM'in kendisi fırlatır. Bu hataların ele alınması ya da bildirilmesi önerilmez; programın düzgün biçimde sonlanması daha uygun bir yaklaşımdır. Yine de Throwable oldukları için teknik olarak yakalanabilirler.
| Hata | Ne zaman? |
|---|---|
OutOfMemoryError | Bellek (yığın bellek, heap) tükendi |
StackOverflowError | Çağrı yığını taştı; genellikle sonsuz özyineleme |
AssertionError | assert koşulu yanlış (yalnız -ea açıkken) |
NoClassDefFoundError | Derlemede var olan sınıf çalışırken bulunamadı |
StackOverflowError fırlatılır.Sunumdaki program üç hatayı da gösterir. Burada Error'ları yakalamanın tek amacı onları ekranda görmektir.
static void sonsuz() {
sonsuz(); // durma koşulu yok
}
public static void main(String[] args) {
try {
sonsuz();
} catch (StackOverflowError e) {
System.out.println("SOE -> " + e);
}
try {
long[] dizi = new long[Integer.MAX_VALUE];
} catch (OutOfMemoryError e) {
System.out.println("OOM -> " + e);
}
int yas = -5;
assert yas >= 0 : "Yaş negatif: " + yas;
System.out.println("assert geçti");
}Program iki biçimde çalıştırılır. java Ornek ile çıktı şudur:
SOE -> java.lang.StackOverflowError
OOM -> java.lang.OutOfMemoryError: Requested array size exceeds VM limit
assert geçtijava -ea Ornek ile ise son satır değişir:
SOE -> java.lang.StackOverflowError
OOM -> java.lang.OutOfMemoryError: Requested array size exceeds VM limit
Exception in thread "main" java.lang.AssertionError: Yaş negatif: -5
at Ornek.main(Ornek.java:17)Birinci try'da sonsuz() metodu kendisini durmadan çağırır. Özyinelemede taban durum unutulursa her çağrı yeni bir çerçeve ekler ve yığın dolar. İkinci try'da yaklaşık iki milyar öğeli bir long dizisi istenir. Bu dizi 16 GB'tan büyüktür; ayrıca HotSpot JVM'inde bir dizinin olabilecek en büyük boyu Integer.MAX_VALUE'dan biraz küçüktür. JVM isteği Requested array size exceeds VM limit iletisiyle reddeder.
Üçüncü kısım assert deyimidir. assert koşul : mesaj; koşul yanlışsa mesajla bir AssertionError fırlatır. Ama doğrulamalar (assert) varsayılan olarak kapalıdır; JVM onları hiç çalıştırmaz. Bu yüzden ilk çalıştırmada assert geçti yazılır. -ea (enable assertions) seçeneğiyle açıldığında koşul sınanır, yas −5 olduğu için hata fırlatılır ve program sonlanır. assert geliştirme sırasında programcının varsayımlarını sınamak içindir; kullanıcı girdisini denetlemek için kullanılmaz, çünkü kapatılabilir.
static int f(int n) { return f(n + 1); } metodu main'de try içinde çağrılıyor; catch (Exception e) yakalandı yazıyor; ardından son yazılıyor. Program çalıştırılınca ne olur?
Hiçbir şey yazılmaz. Her çağrı yığına yeni bir çerçeve ekler; yığın dolunca JVM StackOverflowError fırlatır. Bu bir Error'dur, Exception ailesinden değildir. catch (Exception e) onu yakalamaz; program uzun bir izleme listesiyle sonlanır. Listede aynı at satırı yüzlerce kez tekrarlanır. Derleyici sonsuz özyinelemeyi denetlemez; kod sorunsuz derlenir.
6.10Özel istisnalar
6.10.1Özel istisna sınıfı yazmak
Java kütüphanesindeki istisnalar genel durumları anlatır. Bir bankacılık programındaki “bakiye yetersiz” ya da bir not sistemindeki “not aralık dışında” durumu için kütüphanede özel bir sınıf yoktur. Geliştirici, kendi programında ortaya çıkabilecek bu tür durumları yönetmek için özel istisna sınıfları yazabilir.
Özel istisna sınıfı (custom exception), Exception ya da RuntimeException'dan türetilen ve uygulamaya özgü bir durumu anlatan istisna sınıfıdır.
public class OzelHata extends Exception {
// Constructor
public OzelHata(String message) {
super(message);
}
}Yapıcı, aldığı mesajı super(message) ile üst sınıfa iletir. Mesaj Throwable'ın içinde saklanır; böylece getMessage() ve toString() mesajı kendiliğinden gösterir. super çağrısı unutulursa getMessage() null döndürür.
| Üst sınıf | Sonuç |
|---|---|
extends Exception | Kontrol edilen: çağıran ele almak zorunda; kurtarılabilir iş kuralı ihlalleri |
extends RuntimeException | Kontrol edilmeyen: hatalı kullanım, programcı hatası |
OzelHata Exception'ı genişlettiği için kontrol edilen bir istisnadır.Throwable'ı genişleten her sınıfın nesnesi fırlatılabilir. Ancak genellikle Exception ya da RuntimeException genişletilir. Error JVM'in sorunları için ayrılmıştır. Throwable'ı doğrudan genişletmek ise sınıfı iki ailenin dışında bırakır; catch (Exception e) yazan kod onu yakalayamaz.
Özel istisna yazarken birkaç kural izlenir. Sınıfın adı durumu anlatır ve …Hata ya da …Exception ile biter: YetersizBakiyeHatasi, GecersizNotHatasi. İstisna sıradan bir sınıftır; ek alanlar ve metotlar içerebilir. Eksik tutar, hatalı değer ya da hatalı alanın adı gibi bilgiler alan olarak eklenebilir. Derleyici -Xlint ile çalıştırılırsa serialVersionUID alanının eksik olduğunu da söyler; Throwable serileştirilebilir bir sınıftır. Bu bir uyarıdır, hata değildir.
6.10.2Özel istisnayı fırlatmak ve yakalamak
public class OzelHataTest {
public static void main(String[] args) {
try {
fonksiyon();
} catch (OzelHata e) {
System.out.println("Yakalandı: " + e.getMessage());
}
}
public static void fonksiyon() throws OzelHata {
if (true) {
throw new OzelHata("Özel hata oluştu.");
}
}
}Yakalandı: Özel hata oluştu.OzelHata, Exception'dan türediği için kontrol edilen bir istisnadır. fonksiyon()'un imzasında throws OzelHata zorunludur. main, çağrıyı bir try içine alarak “ele al” seçeneğini kullanır.
Koddaki if (true) yalnız gösterim içindir. throw burada metodun son deyimi olduğu için koşulsuz yazılması da derlenirdi. Ama koşulsuz bir throw'dan sonra herhangi bir kod gelseydi derleyici unreachable statement hatası verirdi. Gerçek programlarda koşul bir iş kuralını denetler: bakiye yeterli mi, not 0 ile 100 arasında mı, kullanıcı adı boş mu?
Kontrol edilen bir GecersizNotHatasi sınıfı ve int notGir(int not) metodu yazalım. Not 0–100 dışındaysa istisna "Not 0-100 arası olmalı: …" mesajıyla fırlatılsın, geçerliyse not dönsün. 85, 120 ve 40 notları bir döngüde girilsin.
class GecersizNotHatasi extends Exception {
GecersizNotHatasi(String m) { super(m); }
}
static int notGir(int not)
throws GecersizNotHatasi {
if (not < 0 || not > 100) {
throw new GecersizNotHatasi(
"Not 0-100 arası olmalı: " + not);
}
return not;
}for (int g : new int[]{85, 120, 40}) {
try {
System.out.println("Kaydedildi: "
+ notGir(g));
} catch (GecersizNotHatasi e) {
System.out.println("Hata: "
+ e.getMessage());
}
}Kaydedildi: 85
Hata: Not 0-100 arası olmalı: 120
Kaydedildi: 40Sınıf Exception'ı genişlettiği için throws zorunludur. try döngünün içindedir: 120'nin hatası yalnız o turu etkiler ve 40'ın kaydedilmesini engellemez. try döngünün dışında olsaydı ilk hatada döngü de biterdi ve 40 hiç denenmezdi.
6.10.3Bilgi taşıyan istisna: banka hesabı
Özel istisnanın asıl gücü, durumu anlatan bilgiyi taşıyabilmesidir. Aşağıdaki sınıf mesaja ek olarak eksik tutarı da saklar.
class YetersizBakiyeHatasi extends Exception {
private double eksik;
public YetersizBakiyeHatasi(String mesaj, double eksik) {
super(mesaj); // mesaj Throwable'da saklanır
this.eksik = eksik;
}
public double getEksik() {
return eksik;
}
}Bu sınıfı kullanan banka hesabı ve deneme kodu şöyledir:
class Hesap {
private double bakiye;
Hesap(double bakiye) { this.bakiye = bakiye; }
public void paraCek(double miktar)
throws YetersizBakiyeHatasi {
if (miktar > bakiye) {
throw new YetersizBakiyeHatasi(
"Bakiye yetersiz", miktar - bakiye);
}
bakiye -= miktar;
System.out.println("Kalan: " + bakiye);
}
}
// main içinde
Hesap hesap = new Hesap(100);
try {
hesap.paraCek(30);
hesap.paraCek(90);
hesap.paraCek(10);
} catch (YetersizBakiyeHatasi e) {
System.out.println(e.getMessage()
+ ", eksik: " + e.getEksik());
}Kalan: 70.0
Bakiye yetersiz, eksik: 20.0| Adım | Satır | Çağrı yığını | Değişkenler | Yeni çıktı |
|---|---|---|---|---|
| 1 | 15: Hesap hesap = new Hesap(100); | main() | hesap=→Hesap | — |
| 2 | 6: if (miktar > bakiye) { | main() → paraCek(30.0) | miktar=30.0, bakiye=100.0 | — |
| 3 | 10: bakiye -= miktar;11: System.out.println("Kalan: " + bakiye); | main() → paraCek(30.0) | miktar=30.0, bakiye=70.0 | Kalan: 70.0 |
| 4 | 6: if (miktar > bakiye) { | main() → paraCek(90.0) | miktar=90.0, bakiye=70.0 | — |
| 5 | 7: throw new YetersizBakiyeHatasi(8: "Bakiye yetersiz", miktar - bakiye); | main() → paraCek(90.0) | miktar=90.0, bakiye=70.0 | — |
| 6 | 20: } catch (YetersizBakiyeHatasi e) { | main() | e=→YetersizBakiyeHatasi | — |
| 7 | 21: System.out.println(e.getMessage()22: + ", eksik: " + e.getEksik()); | main() | e=→YetersizBakiyeHatasi | Bakiye yetersiz, eksik: 20.0 |
1. adımda Hesap nesnesi yığın bellekte oluşur ve hesap onu gösterir; bakiye 100'dür. paraCek(30) çağrısında 30 > 100 yanlıştır; istisna yoktur. Bakiye 70 olur ve Kalan: 70.0 yazılır. 4. adımda paraCek(90) çağrılır. Bu kez 90 > 70 doğrudur.
Hesap ve YetersizBakiyeHatasi. catch parametresi e, fırlatılan istisna nesnesini gösteriyor. Bakiye 70'te kaldı.5. adımda yığın bellekte bir istisna nesnesi oluşur. Nesne iki bilgi taşır: "Bakiye yetersiz" mesajı ve eksik = 90 − 70 = 20.0. Nesne fırlatılır. bakiye -= miktar satırı çalışmadığı için bakiye değişmez. 6. adımda paraCek'in çerçevesi atılır ve paraCek(10) atlanır. catch eşleşir; e fırlatılan nesnenin aynısını gösterir. 7. adımda nesnenin bilgileri kullanılır: Bakiye yetersiz, eksik: 20.0.
Bu örnekte istisnanın da sıradan bir nesne olduğu açıkça görülür. new ile oluşturulur, alanları vardır, metotları çağrılır. Yakalayan kod bu bilgiyle anlamlı bir tepki verebilir. Örneğin kullanıcıya “20 TL eksik; yine de denemek ister misiniz?” diye sorabilir. -1 gibi bir dönüş kodu bu kadar bilgi taşıyamazdı.
Bu sınıf için kontrol edilen tür seçilmiştir. Yetersiz bakiye öngörülebilir bir durumdur ve programcının hatası değildir. Para çekmek isteyen her kod bu olasılıkla başa çıkmalıdır; derleyici de bunu güvenceye alır.
Durumu denetlemeden önce durumu değiştirmek. bakiye -= miktar; satırı if'ten önce yazılsaydı istisna fırlatıldığında bakiye çoktan −20'ye düşmüş olurdu. Nesne bozuk bir durumda kalırdı. İyi bir metot önce bütün denetimleri yapar, istisna gerekiyorsa fırlatır ve ancak ondan sonra nesnenin durumunu değiştirir.
6.11İzleme listesi ve iyi uygulamalar
6.11.1İzleme listesi nedir?
İzleme listesi (stack trace), istisnanın oluştuğu anda çağrı yığınında hangi metotların bulunduğunu ve istisnanın nerede oluştuğunu gösteren rapordur. İlk satır istisnanın tipini ve mesajını verir. Sonraki her at satırı bir çağrının sınıfını, metodunu, dosyasını ve satır numarasını verir.
Yakalanmayan bir istisnada JVM izleme listesini hata akışına (System.err) yazdırır. e.printStackTrace() ile istenen her an da yazdırılabilir. İzleme listesi, programcının hatayı anlamasına ve çözmesine yardım eden en önemli ipucudur.
Exception in thread "main" java.lang.NullPointerException
at com.example.MyClass.myMethod(MyClass.java:10)
at com.example.MyClass.main(MyClass.java:6)Bu listede main metodu 6. satırda myMethod'u çağırmıştır. İstisna myMethod'un 10. satırında oluşmuştur. com.example paketin, MyClass sınıfın adıdır. Liste yukarıdan aşağıya “en son çağrılandan ilk çağrılana” doğru okunur.
Daha ayrıntılı bir örnek üzerinde okuma yöntemine bakalım.
1 public class IzlemeOrnegi {
2 static int uzunluk(String metin) {
3 return metin.length();
4 }
5 static void yazdir(String ad) {
6 System.out.println(ad + ": " + uzunluk(ad));
7 }
8 public static void main(String[] args) {
9 yazdir("Ali");
10 yazdir(null);
11 System.out.println("Bitti");
12 }
13 }Ali: 3
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "metin" is null
at IzlemeOrnegi.uzunluk(IzlemeOrnegi.java:3)
at IzlemeOrnegi.yazdir(IzlemeOrnegi.java:6)
at IzlemeOrnegi.main(IzlemeOrnegi.java:10)İzleme listesi dört adımda okunur:
- İlk satır tip ve mesajı verir. Burada
metindeğişkeninull'dır ve üzerindelength()çağrılamamıştır. - İlk
atsatırı istisnanın doğduğu yerdir:uzunlukmetodu, 3. satır. - Aşağı doğru çağrı zinciri okunur:
main(10) →yazdir(6) →uzunluk(3). - Önce programın kendi satırlarına bakılır. Kütüphane sınıflarının satırları (
java.ile başlayanlar) çoğunlukla sorunun kaynağı değildir.
Asıl neden çoğu zaman listenin aşağısındadır. İstisna 3. satırda doğmuştur, ama 3. satırda bir yanlış yoktur: uzunluk metodu kendisine verilen metnin uzunluğunu doğru biçimde hesaplar. null değerini 10. satır göndermiştir. Düzeltilecek yer çoğu zaman istisnanın doğduğu yer değil, hatalı değeri üreten yerdir.
İletideki "metin" adı, sınıf hata ayıklama bilgisiyle (javac -g) derlendiğinde görünür. Düz javac ile derlenen aynı program adı bilemez ve şunu yazar:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<parameter1>" is nullSatır numaraları ise her iki durumda da yazılır, çünkü javac satır bilgisini varsayılan olarak sınıf dosyasına koyar.
6.11.2Kod izleme: izleme listesi nereden gelir?
İzleme listesindeki satırlar bir anlık fotoğraftır: istisna nesnesi oluştuğu anda çağrı yığınının kaydını alır. Sunumdaki adım adım izleme bu fotoğrafın nasıl çekildiğini gösterir.
| Adım | Satır | Çağrı yığını | Değişkenler | Yeni çıktı |
|---|---|---|---|---|
| 1 | 9: yazdir("Ali"); | main:9 | — | — |
| 2 | 6: System.out.println(ad + ": " + uzunluk(ad)); | main:9 → yazdir:6 | ad=Ali | — |
| 3 | 3: return metin.length(); | main:9 → yazdir:6 → uzunluk:3 | metin=Ali | — |
| 4 | 6: System.out.println(ad + ": " + uzunluk(ad)); | main:9 → yazdir:6 | ad=Ali | Ali: 3 |
| 5 | 10: yazdir(null); | main:10 | — | — |
| 6 | 6: System.out.println(ad + ": " + uzunluk(ad)); | main:10 → yazdir:6 | ad=null | — |
| 7 | 3: return metin.length(); | main:10 → yazdir:6 → uzunluk:3 | metin=null | — |
| 8 | 3: return metin.length(); | main:10 → yazdir:6 → uzunluk:3 | metin=null | — |
| 9 | 10: yazdir(null); | — | — | Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "metin" is null ⏎ at IzlemeOrnegi.uzunluk(IzlemeOrnegi.java:3) ⏎ at IzlemeOrnegi.yazdir(IzlemeOrnegi.java:6) ⏎ at IzlemeOrnegi.main(IzlemeOrnegi.java:10) |
1. adımda main, 9. satırda yazdir("Ali")'yi çağırır. yazdir 6. satırda uzunluk(ad)'ı çağırır. Her çerçeve kendi metodunun hangi satırda olduğunu hatırlar. "Ali".length() 3 döndürür ve çerçeve silinir. Ali: 3 yazılır ve yazdir biter.
5. adımda main, 10. satırda yazdir(null)'ı çağırır. null parametre olarak iletilir; bu noktada henüz bir sorun yoktur. Bir referansın null olması hata değildir; null üzerinden metot çağırmak hatadır. 7. adımda null.length() denenir ve NPE fırlatılır. O anda yığında üç çerçeve vardır: uzunluk:3, yazdir:6, main:10.
at satırı olur; yığının tepesindeki çerçeve listenin ilk satırıdır.8. adımda istisna nesnesi yığının kaydını alır. Her çerçeve bir at satırına dönüşür ve en üstteki çerçeve ilk sıraya yazılır. Listenin “ters” sırası buradan gelir: yığının tepesi listenin başıdır. Son adımda kimse istisnayı yakalamamıştır. Üç çerçeve de atılır; JVM izleme listesini yazar ve programı sonlandırır. Bitti yazılmaz.
Aşağıdaki program ne yazdırır? İzleme listesine göre istisna hangi satırda doğar, null değeri nereden gelir?
1 public class Okul {
2 public static void main(String[] args) {
3 int[] a = {70, 80};
4 int[] b = null;
5 rapor("A", a);
6 rapor("B", b);
7 System.out.println("bitti");
8 }
9 static void rapor(String ad, int[] d) {
10 System.out.println(ad + ": " + ortalama(d));
11 }
12 static double ortalama(int[] notlar) {
13 int t = 0;
14 for (int n : notlar) {
15 t += n;
16 }
17 return (double) t / notlar.length;
18 }
19 }A: 75.0
Exception in thread "main" java.lang.NullPointerException: Cannot read the array length because "<local2>" is null
at Okul.ortalama(Okul.java:14)
at Okul.rapor(Okul.java:10)
at Okul.main(Okul.java:6)İlk at satırı istisnanın doğduğu yeri gösterir: ortalama, 14. satır. Bu satırdaki for-each döngüsü, null bir dizinin uzunluğunu okumaya çalışır. Mesaj da bunu söyler: Cannot read the array length. Değişken adı yerine <local2> görünür, çünkü program -g olmadan derlenmiştir. Zincir aşağıdan yukarı main (6) → rapor (10) → ortalama (14)'tür. null, 4. satırda b'ye atanmış ve 6. satırda gönderilmiştir. Kimse yakalamadığı için 7. satır çalışmaz.
6.11.3İstisna yönetiminde iyi uygulamalar
İstisna düzeneğini doğru kullanmak, onu tanımak kadar önemlidir. Aşağıdaki sekiz kural, örneklerde görülen dersleri bir araya toplar.
- Özel tipi yakalamak
- Mümkünse
catch (Exception e)yerine beklenen tip yakalanır ve her türe uygun tepki verilir. Genel tip, beklenmeyen hataları da sessizce yutar. - İstisnayı yutmamak
- Boş bir
catch { }hatayı gizler. İstisna en azından kayda geçirilir ya da kullanıcıya bildirilir. - Kaynakları kapatmak
- Dosya ve bağlantı gibi kaynaklar try-with-resources ya da
finallyile kapatılır. - Önlenebilecekleri önlemek
null, sınır ve sıfır denetimleri yapılır.RuntimeExceptionyakalamak yerine kod düzeltilir.- Akış denetimi için kullanmamak
- Döngüden çıkmak gibi olağan işler istisnayla yapılmaz. İstisnalar istisnai durumlar içindir; ayrıca istisna nesnesi oluşturmak ve yığını kaydetmek sıradan bir
if'ten çok daha pahalıdır. - Anlamlı mesaj
- Mesaj hatalı değeri ve beklentiyi söyler:
"Not 0-100 arası olmalı: 120". - Doğru yerde ele almak
- İstisna çözülebileceği yerde yakalanır; çözülemiyorsa
throwsile iletilir. - Kayıt tutmak
- İstisnaları bir dosyaya kaydetmek (logging) hatanın izini sonradan sürmeyi kolaylaştırır. Kullanıcının ekranında kaybolan bir ileti, kayıt dosyasında kalır.
İstisnayı yutmak: catch (Exception e) { }. Bu kod derlenir ve çalışır, ama hata iz bırakmadan kaybolur. Program yanlış bir durumla çalışmayı sürdürür ve sorun çok sonra, ilgisiz bir yerde ortaya çıkar. O zaman asıl nedenin izleme listesi çoktan yok olmuştur.
6.11.4final, finally ve finalize
Üç adın yazılışı benzer, işleri tamamen farklıdır.
| Ne? | Görevi | |
|---|---|---|
final | Anahtar kelime | Değişken: yeniden atanamaz · metot: geçersiz kılınamaz · sınıf: alt sınıfı olamaz |
finally | Anahtar kelime (blok) | try'dan sonra, istisna olsun olmasın her durumda çalışan temizlik bloğu |
finalize() | Object sınıfının metodu | Çöp toplayıcı nesneyi silmeden önce çağırabilirdi. Ne zaman çağrılacağı belirsizdir; Java 9'dan beri kullanımdan kaldırılmış (deprecated) durumdadır. |
finalize()'ın sorunu güvenilmez olmasıdır. Çöp toplayıcının bir nesneyi ne zaman sileceği, hatta silip silmeyeceği belli değildir. Program biterken hiç çalışmamış olabilir. Java 18'den beri bu metot ileride tamamen kaldırılmak üzere işaretlenmiştir; onu geçersiz kılan bir sınıf derlenirken -Xlint şu uyarıyı verir: finalize() in Object has been deprecated and marked for removal. Kaynak temizliği için finalize() değil, try-with-resources ya da finally kullanılır.
6.12Çözümlü problemler
Aşağıdaki problemler sunumun örnek problemleri ve alıştırmalarıdır. Her çözüm, gerekçesiyle birlikte adım adım verilir. Bütün çıktılar JDK 21 ile çalıştırılarak doğrulanmıştır.
Aşağıdaki kod ne yazdırır?
static int f() {
try {
System.out.print("A");
int x = 5 / 0;
System.out.print("B");
return 1;
} catch (ArithmeticException e) {
System.out.print("C");
return 2;
} finally {
System.out.print("D");
}
}
// main içinde
System.out.println(" " + f());Çözüm. main, println'ın argümanını hesaplamak için önce f()'yi çağırır. try başlar ve A yazılır. 5 / 0 istisna fırlatır; B ve return 1 atlanır. catch eşleşir, C yazar ve return 2 ile 2'yi hazırlar. Metottan çıkmadan önce finally çalışır ve D yazar. Ardından 2 döner. println, " " + 2 değerini yazar. Sonuç: ACD 2. print satır sonu eklemediği için dört parça aynı satırdadır.
try {
String s = "12a";
int n = Integer.parseInt(s);
System.out.println("Sayı: " + n);
} catch (IllegalArgumentException e) {
System.out.println("IAE");
} catch (Exception e) {
System.out.println("Exception");
}Kod ne yazdırır? İki catch bloğunun yeri değiştirilirse ne olur?
Çözüm. "12a" geçerli bir tam sayı değildir; parseInt bir NumberFormatException fırlatır. Birinci catch IllegalArgumentException içindir. NumberFormatException bu sınıfın alt sınıfı olduğu için birinci blok eşleşir: çıktı IAE'dir. İkinci blok denenmez.
Bloklar yer değiştirirse önce catch (Exception e) gelir. Bu blok IllegalArgumentException'ı da kapsar ve ikinci blok erişilemez olur. Kod derlenmez:
Ana.java:12: error: exception IllegalArgumentException has already been caught
} catch (IllegalArgumentException e) {static void a() {
System.out.print("a1 ");
b();
System.out.print("a2 ");
}
static void b() {
System.out.print("b1 ");
throw new IllegalStateException("!");
}
// main içinde
try {
a();
System.out.print("m ");
} catch (IllegalStateException e) {
System.out.print("yakalandı" + e.getMessage());
} finally {
System.out.println(" son");
}İstisna b()'de doğuyor; b()'de ve a()'da try yok. Hangi print'ler çalışır? finally ne zaman çalışır?
Çözüm. main a()'yı çağırır: a1 yazılır. a, b()'yi çağırır: b1 yazılır. b bir IllegalStateException fırlatır. b'de catch yoktur; çerçeve atılır. İstisna a'ya geçer; a'da da catch yoktur ve a2 atlanır. İstisna main'e ulaşır; try'ın kalanı (m ) atlanır. catch eşleşir ve yakalandı! yazar; mesaj "!" olduğu için iki parça bitişiktir. En son finally son yazar ve satırı bitirir. Sonuç: a1 b1 yakalandı! son.
int[] d = {4, 0, 2};
int toplam = 0;
for (int i = 0; i <= d.length; i++) {
try {
toplam += 12 / d[i];
} catch (ArithmeticException e) {
toplam += 100;
} catch (ArrayIndexOutOfBoundsException e) {
toplam += 1000;
}
}
System.out.println(toplam);toplam kaçtır?
Çözüm. Döngü koşulu i <= d.length'tir; döngü 0, 1, 2 ve 3 için, yani dört tur döner. Her turun kendi try'ı vardır.
i = 0:12 / 4 = 3; toplam 3.i = 1:12 / 0→ArithmeticException;+100, toplam 103.i = 2:12 / 2 = 6; toplam 109.i = 3:d[3]→ArrayIndexOutOfBoundsException;+1000, toplam 1109.
Çıktı 1109'dur. try döngünün içinde olduğu için bir turdaki istisna döngüyü bitirmez. 1. turda toplam += 12 / d[i] ifadesinde bölme atamadan önce hesaplanır; istisna oluşunca toplama hiçbir şey eklenmez.
try {
throw new RuntimeException("X");
} finally {
System.out.println("F");
}Kod ne yazdırır?
Çözüm. try bir RuntimeException fırlatır. catch yoktur, ama finally vardır. İstisna metottan çıkmadan önce finally çalışır ve F yazar. Ardından istisna yoluna devam eder; main'de de yakalanmadığı için program sonlanır:
F
Exception in thread "main" java.lang.RuntimeException: X
at P6.main(P6.java:4)Metni yaşa çeviren int yasOku(String metin) metodunu yazalım. Metin sayı değilse NumberFormatException kendiliğinden oluşsun; sayı negatifse metot IllegalArgumentException fırlatsın. "20", "-3" ve "yirmi" girdileri iki catch ile denensin. catch sırası ters yazılırsa ne olur?
Çözüm. Metot üç satırdır: int yas = Integer.parseInt(metin);, if (yas < 0) throw new IllegalArgumentException("Yaş negatif: " + yas); ve return yas;. Çağıran kodda önce catch (NumberFormatException e) (“Sayı değil”), sonra catch (IllegalArgumentException e) yazılır. Çıktı: Yaş: 20, Yaş negatif: -3, Sayı değil: yirmi.
Sıra ters yazılırsa NumberFormatException bloğuna hiçbir zaman ulaşılamaz, çünkü NFE bir IllegalArgumentException'dır. Derleyici şu hatayı verir: exception NumberFormatException has already been caught.
Bir BufferedReader'ı finally ile kapatan kod try-with-resources ile nasıl yazılır? double ortalama(int[] notlar) metodu boş dizi için ne yapmalıdır?
Çözüm. Okuyucu parantezde açılır: try (BufferedReader r = new BufferedReader(new FileReader(yol))) { … } catch (IOException e) { … }. close() otomatik çağrılır; finally, null denetimi ve iç içe try gerekmez.
Boş dizinin ortalaması tanımsızdır. 0 döndürmek yanlıştır, çünkü 0 geçerli bir ortalamayla karışır; bu, -1 dönüş kodunun sorununun aynısıdır. Boş dizi geçersiz bir argümandır: throw new IllegalArgumentException("Dizi boş");. Kontrol edilmeyen bir tip seçilir, çünkü boş dizi göndermek çağıranın hatasıdır. Deneme programında ortalama(new int[0]) bu istisnayı verdi, {70, 80, 90} dizisi ise 80.0 döndürdü.
6.13Sık yapılan hatalar
İstisnalarla ilgili hatalar iki gruba ayrılır. Derleme hataları çoğunlukla kontrol edilen istisna kuralından ve catch sırasından doğar; iletileri açıktır. Mantık hataları ise derleyiciden geçer ve programın davranışını sessizce bozar. Aşağıdaki iki tablo, notun gövdesinde ayrı ayrı işlenen hataları bir arada verir. Derleme iletileri JDK 21'in javac aracıyla alınmıştır.
| Hata | javac iletisi | Neden / çözüm |
|---|---|---|
Kontrol edilen istisnayı unutmak: new FileReader(ad), try ya da throws yok | unreported exception FileNotFoundException; must be caught or declared to be thrown | Ya try-catch ya da throws. |
Genel catch'i öne yazmak: önce Exception, sonra ArithmeticException | exception ArithmeticException has already been caught | Alt sınıf önce, üst sınıf sonra. |
Fırlatılmayanı yakalamak: dosya işlemi olmayan try'a catch (FileNotFoundException e) | exception FileNotFoundException is never thrown in body of corresponding try statement | catch kaldırılır. |
Yalnız try yazmak | 'try' without 'catch', 'finally' or resource declarations | En az bir catch ya da finally. |
throw'dan sonra aynı blokta kod | unreachable statement | Satır kaldırılır ya da throw bir koşula bağlanır. |
try içindeki değişkeni dışarıda kullanmak | cannot find symbol | Değişken try'dan önce tanımlanır. |
Metin fırlatmak: throw "hata"; | incompatible types: String cannot be converted to Throwable | Bir istisna nesnesi fırlatılır. |
Gövdede throws | illegal start of expression | Gövdede throw, imzada throws. |
| Çoklu yakalamada alt ve üst sınıf | Alternatives in a multi-catch statement cannot be related by subclassing | Yalnız üst tip yazılır. |
AutoCloseable olmayan kaynak | incompatible types: try-with-resources not applicable to variable type | Sınıf AutoCloseable'ı uygular. |
| Hata | Sonuç | Düzeltme |
|---|---|---|
İstisnayı yutmak: catch (Exception e) { } | Hata iz bırakmadan kaybolur | En azından kayıt tutulur ya da bildirilir |
Dönüş kodunu denetlememek (-1) | Hata başka bir yerde, başka bir istisnayla ortaya çıkar | Dönüş kodu yerine istisna |
finally içinde return | Bekleyen istisna ve dönüş değeri sessizce silinir | finally yalnız temizlik yapar |
Kaynağı try'ın sonunda kapatmak | İstisnada kaynak açık kalır (sızıntı) | try-with-resources |
catch (Exception e)'nin her şeyi yakaladığını sanmak | StackOverflowError gibi Error'lar yakalanmaz | Error'lar yakalanmaz; neden düzeltilir |
| NPE'yi yakalayıp geçmek | null'ın kaynağı gizlenir | null'ın nereden geldiği bulunur |
| Mesajsız istisna fırlatmak | İleti hatalı değeri söylemez | Değer ve beklenti mesaja yazılır |
| İstisnayı döngü denetimi için kullanmak | Okunması zor ve yavaş kod | Olağan durumlar if ile denetlenir |
| Denetimden önce durumu değiştirmek | İstisnadan sonra nesne bozuk kalır | Önce denetim, sonra değişiklik |
6.14Özet
- İstisna, program çalışırken normal akışı bozan durumdur. Java'da istisna bir nesnedir; tipini, mesajını ve oluştuğu yeri taşır. JVM ya da programcı (
throw) oluşturabilir. - Çalışma zamanı sorunlarının iki kolu vardır: program toparlanabiliyorsa
Exception, JVM ya da sistem düzeyinde ciddi bir sorun varsaError. - İstisna oluşabilen bir metot onu ya ele alır (
try-catch) ya da çağırana bildirir (throws). Doğru yer, istisnayı anlamlı biçimde çözebilecek yerdir. - Özel dönüş kodları (
-1) hatayı normal sonuçla karıştırır ve denetlenmezse sessizce gözden kaçar. İstisna bu sorunları çözer. try'da istisna oluşursa bloğun kalanı atlanır ve eşleşen ilkcatchçalışır.catch'ten sonra program normal akışına devam eder. İstisna yoksacatchçalışmaz.catchblokları yukarıdan aşağıya denenir. Bir blok, yazılan tipi ve bütün alt sınıflarını yakalar. Alt sınıf üst sınıftan önce yazılır; tersihas already been caughtderleme hatasıdır.- Java 7'den beri
catch (A | B e)aynı tepkiyi tek blokta toplar. Alternatifler birbirinin alt sınıfı olamaz;eörtük olarakfinal'dır. finallyher durumda çalışır;tryya dacatchiçindekireturn'den bile önce. Dönüş değeri önce hesaplanıp saklanır. YalnızSystem.exit()finally'yi engeller.- try-with-resources, parantezde açılan
AutoCloseablekaynakları otomatik kapatır. Kaynaklar yazılış sırasıyla açılır, ters sırayla vecatch'ten önce kapanır. - İç içe
try'da iç düzeyde yakalanmayan istisna dışa doğru ilerler. Geçtiği her düzeyinfinally'si çalışır. - Hiyerarşinin kökü
Throwable'dır; iki alt sınıfıErrorveException'dır.RuntimeException,Exception'ın bir alt sınıfıdır. - Kontrol edilen istisnalar (
Exception'dan türeyipRuntimeException'dan türemeyenler) için derleyici “yakala ya da bildir” kuralını uygular.RuntimeExceptionveErroraileleri kontrol edilmez. throwgövdede bir nesne fırlatır;throwsimzada fırlatılabilecek tipleri bildirir.- Yakalanmayan istisna çağrı yığınında çağırana doğru yayılır; yol üzerindeki metotlar o anda sona erer. Bu sürece yığın çözülmesi denir.
mainde yakalamazsa JVM izleme listesini yazar ve programı sonlandırır. - Sık görülen çalışma zamanı istisnaları:
NullPointerException,ArrayIndexOutOfBoundsException,ArithmeticException,IllegalArgumentException,NumberFormatException,ClassCastException. Bunlar çoğunlukla yakalanmaz, önlenir. doubleişlemleri istisna fırlatmaz (Infinity,NaN);inttoplaması taşınca sessizce başa sarar. Taşma denetimi içinMath.addExactkullanılır.- Sık görülen kontrol edilen istisnalar:
IOExceptionve alt sınıfıFileNotFoundException,SQLException,ParseException,ClassNotFoundException,InterruptedException. Error'lar (OutOfMemoryError,StackOverflowError,AssertionError) uygulama kodunda yakalanmaz.assertyalnız-eaile açılır.- Özel istisna sınıfı
Exception(kontrol edilen) ya daRuntimeException(kontrol edilmeyen) genişletilerek yazılır; mesajsuper(mesaj)ile iletilir. İstisna, ek alanlarla durumu anlatan bilgiyi taşıyabilir. - İzleme listesinin ilk satırı tip ve mesajdır; ilk
atsatırı istisnanın doğduğu yerdir. Aşağı doğru çağrı zinciri okunur. Asıl neden çoğu zaman hatalı değeri üreten satırdadır. - İyi uygulamalar: özel tipi yakalamak, istisnayı yutmamak, kaynakları kapatmak ve önlenebileni önlemek. İstisna akış denetimi için kullanılmaz; mesaj anlamlı olur, istisna doğru yerde ele alınır ve kayda geçirilir.
6.15Kavram listesi
- istisna exception
- çalışma zamanı hatası runtime error
- mantık hatası logic error
- Error error (Error class)
- fırlatmak / yakalamak throw / catch
- try-catch bloğu try-catch block
- finally bloğu finally block
- try-with-resources try-with-resources
- kontrol edilen istisna checked exception
- kontrol edilmeyen istisna unchecked exception
- istisna yayılımı exception propagation
- çağrı yığını call stack
- izleme listesi stack trace
- özel istisna sınıfı custom exception
- hata ayıklama debugging
- doğrulama (assert) assertion
- serileştirme serialization
- çöp toplayıcı garbage collector
- taşma overflow
- açık dönüşüm (cast) cast (explicit conversion)
- jenerikler generics
- metot imzası method signature
- yığın çözülmesi stack unwinding
- çoklu yakalama multi-catch
- kaynak sızıntısı resource leak
- örüntü eşleme pattern matching
6.16Kendini Dene
Dış
tryiçindeAyazılıyor. İçtrythrow new RuntimeException("x")yapıyor; içcatchyalnızIllegalStateExceptionyakalayıpByazıyor; içfinallyCyazıyor. İç bloktan sonra dıştry'daDyazılıyor. Dışcatch (RuntimeException e)dış:ile mesajı yazıyor. Bütün yazmalarprintile yapılıyorsa çıktı nedir?Cevap
AC dış:x.RuntimeException,IllegalStateException'ın üst sınıfıdır, alt sınıfı değil; içcatcheşleşmez veByazılmaz. İstisna dışarı çıkmadan önce içfinallyCyazar.Datlanır ve dışcatchyakalar.static int f() { try { return 1; } finally { return 2; } }metodu ne döndürür? Bu yazım neden önerilmez?Cevap
2.
try'dakireturn 1değeri hazırlar, ama metottan çıkmadan öncefinallyçalışır ve kendireturn'ü bekleyen değerin yerini alır. Aynı yazım,try'da fırlatılan bir istisnayı da sessizce yok ederdi.javac -Xlintbu yüzdenfinally clause cannot complete normallyuyarısı verir.Integer.parseInt("12 ")veDouble.parseDouble(" 12 ")çağrılarının sonuçları nedir?Cevap
Birincisi
NumberFormatException: For input string: "12 "fırlatır;parseIntsondaki boşluğu kabul etmez ve öncetrim()gerekir. İkincisi12.0döndürür:Double.parseDoublebaştaki ve sondaki boşlukları kendisi atar. İki benzer metodun farklı davranması, kullanıcı girdisini her zamantrim()ile temizlemenin bir gerekçesidir.try-with-resources parantezinde
K a = new K("A"); K b = new K(null)yazılı.K'nın yapıcısı adnulliseIllegalArgumentException("ad yok")fırlatıyor, değilse… açıldıyazıyor;close()… kapandıyazıyor. Bloktaişyazılıyor;catchcatchile mesajı yazıyor. Çıktı nedir?Cevap
A açıldı,A kapandı,catch ad yok. İkinci kaynağın yapıcısı istisna fırlattığı içinbhiç oluşmaz ve blok gövdesi çalışmaz. Başarıyla açılmış olanayine kapatılır. Yalnız oluşturulabilen kaynaklar kapatılır.catch (IOException | Exception e)derlenir mi?static void a() throws IOExceptionvestatic void b() throws RuntimeExceptionmetotlarımain'detryolmadan çağrılırsa hangi çağrı hata verir?Cevap
Çoklu yakalama derlenmez:
IOException,Exception'ın alt sınıfıdır ve alternatifler kalıtımla ilişkili olamaz.a()çağrısıunreported exception IOExceptionhatası verir.b()derlenir:RuntimeExceptionkontrol edilmez; bildirimi yalnız belgeleme işi görür.Integer.MAX_VALUE * 2veMath.multiplyExact(Integer.MAX_VALUE, 2)ne verir?Cevap
Birincisi istisnasız
-2verir: sonuçintaralığını aşar ve değer başa sarar (sessiz taşma). İkincisiArithmeticException: integer overflowfırlatır. Kesin metotlar taşmayı görünür kılar.class X extends Exception { X(String m) { } }sınıfı içinnew X("m").getMessage()ne döndürür?Cevap
null. Yapıcı mesajısuper(m)ile üst sınıfa iletmemiştir. Derleyici örtük olarak parametresizsuper()'ı çağırır veThrowable'ın mesaj alanı boş kalır. Doğru yazımX(String m) { super(m); }'dir.Bir izleme listesi şöyledir:
java.lang.ArithmeticException: / by zero, ardındanat Hesap.oran(Hesap.java:12),at Hesap.rapor(Hesap.java:8),at Hesap.main(Hesap.java:3). İstisna nerede doğmuştur?mainhangi metodu hangi satırda çağırmıştır?Cevap
İstisna ilk
atsatırında,oranmetodunun 12. satırında doğmuştur. Liste aşağıdan yukarı okunursamain3. satırdarapor'u,raporda 8. satırdaoran'ı çağırmıştır. Sıfır payda büyük olasılıklaraporya damaintarafında üretilmiştir; düzeltme için oradan geriye bakılır.
6.17İleri okuma
- Oracle, The Java Tutorials, “Lesson: Exceptions”:
try,catch,finally, try-with-resources ve “Unchecked Exceptions — The Controversy” sayfalarıyla konunun resmî ve ücretsiz anlatımıdır. - J. Bloch, Effective Java (3. baskı), Bölüm 10, Madde 69–77: istisnaların yalnız istisnai durumlar için kullanılmasını, kontrol edilen ve edilmeyen tip seçimini ve istisnaların yutulmamasını gerekçeleriyle tartışır.
- C. S. Horstmann, Core Java, Volume I: Fundamentals (12. baskı), Bölüm 7 “Exceptions, Assertions, and Logging”: istisnaları
assertve kayıt tutma ile birlikte, uygulamaya dönük biçimde işler. - J. Gosling ve diğerleri, The Java Language Specification, Java SE 21 Edition, Bölüm 11 “Exceptions” ve 14.20 “The try statement”: kontrol edilen istisna kuralının ve
finally'nin kesin tanımıdır.
6.18Kaynaklar
- Oracle, The Java Tutorials: Lesson: Exceptions (Catching and Handling Exceptions; The try-with-resources Statement; Unchecked Exceptions — The Controversy).
- J. Gosling ve diğerleri, The Java Language Specification, Java SE 21 Edition, Bölüm 11: Exceptions; 14.20 The try statement.
- Oracle, Java SE 21 API Documentation:
java.lang.Throwable,Exception,RuntimeException,Error. - P. Deitel ve H. Deitel, Java: How to Program, 11. baskı, Pearson (Bölüm 11: Exception Handling: A Deeper Look).
- K. Sierra, B. Bates ve T. Gee, Head First Java, 3. baskı, O'Reilly (Risky Behavior: exception handling).
- C. S. Horstmann, Core Java, Volume I: Fundamentals, 12. baskı, Pearson (Bölüm 7: Exceptions, Assertions, and Logging).
- J. Bloch, Effective Java, 3. baskı, Addison-Wesley (Bölüm 10: Exceptions, Madde 69–77).
İlgili sayfalar: Bölüm 6 sunumu · Çalışma soruları · İstisna Akışı simülatörü · Terim sözlüğü · © Sercan Külcü. Tüm hakları saklıdır.