Press "Enter" to skip to content

Effective Java Madde 9: try-finally Yerine try-with-resources Tercih Edin

Java kütüphaneleri yazılımcının close metodunu çağırarak kapatması gereken birçok kaynak barındırır. Bunlara örnek olarak InputStream, OutputStream ve java.sql.Connection verilebilir. Bu kaynakları kapatmak (serbest bırakmak), istemciler tarafından sıklıkla unutulur ve bu durum ciddi performans sorunlarına yol açar. Bu kaynaklar her ne kadar finalizer (sonlandırıcı) kullanarak istemci tarafında yapılacak hatalara bir önlem almaya çalışsalar da, pratikte biz sonlandırıcıların iyi çalışmadığını biliyoruz. (Madde 8)

Önceleri, try-finally ifadesi kullandığımız kaynakların bir istisna (exception) fırlatıldığında dahi doğru biçimde kapatılmasını garanti eden en iyi yoldu.

// try-finally - Artık kaynakları kapatmanın en iyi yolu değil!
static String firstLineOfFile(String path) throws IOException { 
    BufferedReader br = new BufferedReader(new FileReader(path)); 
    try {
        return br.readLine();
    } finally {
        br.close();
    }
}

Bu çok da kötü görünmüyor, ancak ikinci bir kaynak daha eklediğimizde durum kötüleşiyor.

// try-finally birden fazla kaynakla kullanılınca kod çirkinleşiyor
static void copy(String src, String dst) throws IOException {
    InputStream in = new FileInputStream(src);
    try {
        OutputStream out = new FileOutputStream(dst);
        try {
            byte[] buf = new byte[BUFFER_SIZE];
            int n;
            while ((n = in.read(buf)) >= 0) { 
                out.write(buf, 0, n);
            }
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}   

İnanması zor olabilir ama, iyi programcılar bile çoğu zaman bunu yanlış kullanmaktadır. Örneğin, Java Puzzlers kitabının 88. sayfasında ben de hata yaptım ve yıllarca hiç kimse fark etmedi. Dahası, 2007 yılında Java kütüphanelerindeki close metodu kullanımlarının üçte ikisi yanlıştı.

try-finally blokları doğru kullanılarak kod yazıldığı durumlarda bile (yukarıdaki iki örnek gibi), ortada küçük de olsa bir sorun bulunmaktadır. Hem try hem de finally bloklarındaki kodların istisna fırlatma ihtimali bulunmaktadır. Örneğin, ilk örnekteki firstLineOfFile metodunda, try bloğundakireadLine çağrısı fiziksel aygıttaki olası bir problemden dolayı istisna fırlatabilir, finally içerisindeki close çağrısı da aynı sebepten dolayı başarısız olabilir. Bu durumda, ikinci fırlatılan istisna birinciyi tamamen görünmez kılacaktır. Stack trace içerisinde birinci istisna görülmeyecektir, bu da problemi çözmeye çalışan kişileri çok zor durumda bırakacaktır. Çünkü genellikle bir sorunu çözmeye çalışırken ilk hatanın nereden kaynaklandığını görmek önemlidir. Bu sorunu çözmek için yukarıda verilen kodları geliştirmek mümkündür, ancak sonuçta oluşan kod daha karmaşık olduğu için bunu genellikle kimse yapmaz.

Bütün bu problemler Java 7 ile birlikte gelen try-with-resources ifadesiyle çözülmüştür. Bu ifadeyi kullanabilmek için kapatılması gereken kaynakların, void geri döndüren tek bir close metodu içeren AutoClosable arayüzünü uygulaması gerekmektedir. Java kütüphanelerindeki birçok sınıf ve arayüz bugün bu arayüzü kullanmaktadır. Eğer siz de kapatılması gereken bir kaynağı temsil eden bir sınıf yazarsanız, AutoClosable arayüzünü mutlaka uygulamalısınız.

Yukarıda verdiğimiz birinci örneğin try-with-resources ile kullanımı aşağıdaki gibi olacaktır.

// try-with-resources - kaynakları kapatmanın en iyi yolu!
static String firstLineOfFile(String path) throws IOException {
  try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  } 
}

Ve bu da ikinci örneğimizin try-with-resources ile yazılışı:

// try-with-resources birden fazla kaynakla kullanımı 
static void copy(String src, String dst) throws IOException {
    try (InputStream   in = new FileInputStream(src);
         OutputStream out = new FileOutputStream(dst)) {
        
        byte[] buf = new byte[BUFFER_SIZE];
        int n;
        while ((n = in.read(buf)) >= 0) {
            out.write(buf, 0, n);
        }
    }
}

Burada gördüğünüz gibi, istemcinin ayrıca close metodunu çağırmasına gerek kalmamaktadır. try-with-resources kullanıldığında AutoClosable arayüzünden gelen close metodu otomatik olarak çağrılmakta ve istemci tarafında yapılacak hatalara karşı önlem alınmaktadır.

try-with-resources ifadesiyle yazılan kodlar daha kısa ve okunabilir olmasının yanında, hataların teşhisini de kolaylaştırmaktadır. firstLineOfFile metodunu düşünecek olursak, eğer hem readLine metodu hem de görünmez close metodu istisna fırlatırsa, ikinci istisna birinciyi görünmez kılmaz, tam tersine birinci görülebilsin diye ikinci gizlenir (suppressed). Hatta ikiden daha fazla istisna fırlatıldığı durumlarda birden çok istisna da gizlenebilir. Çünkü yazılımcının ilk oluşan hatayı görebilmesi hatanın teşhisi açısından önemlidir. Burada saklanan istisnalar da aslında kaybolmazlar, stack trace içerisinde yine görülebilirler ancak gizlendiklerine dair bir ibare yer alır. Hatta bunlara Java 7’de Throwable sınıfına eklenen getSuppressed metoduyla programatik olarak da erişmek mümkündür.

try-with-resources kullanırken aynen try-finally kullanırken olduğu gibi catch blokları eklemek de mümkündür. Bu, sizlere iç içe katmanlar eklemenize gerek kalmadan kodunuzda oluşabilecek istisnaları ele almanızı sağlar. Pratikte kullanımı pek mantıklı gözükmese de, ilk örneğimizi aşağıdaki gibi değiştirerek istisna fırlatmak yerine, veri okumada bir hata oluştuğunda varsayılan bir değeri döndürecek şekilde yazabiliriz.

// try-with-resources catch bloğuyla kullanımı
static String firstLineOfFile(String path, String defaultVal) {
  try (BufferedReader br = new BufferedReader(new FileReader(path))) {
    return br.readLine();
  } 
  catch (IOException e) {
    return defaultVal;
  } 
}

Buradaki ders çok net: kapatılması gereken kaynaklarla çalışırken try-finally yerine try-with-resources ifadesini kullanmalıyız. Bu sayede ortaya çıkan kod çok daha kısa ve anlaşılır olacaktır, oluşabilecek hatalar sonucunda da teşhis yapmamız kolaylaşacaktır. Bu yeni yöntem, try-finally ile yazılması çok zor ve zahmetli olan kod parçalarının doğru biçimde yazılmasını son derece kolaylaştırmaktadır.

Share

Leave a Reply

%d bloggers like this: