post-images/try-finally-vs-try-with-resources/cover.jpg

try-finally vs try-with-resources

Selamlar 🙋🏻‍♂️ Bugün Effective Java kitabının ilk ünitesi olan Creating and Destroying Objects'in son maddesini ele alacağız.

Bu yazı içerisinde java kütüphanelerinin bir çoğunda bulunan (InputStream, OutputStream vb) manuel close metodu gereksiniminden bahsedeceğiz ve bu gereksinimi en doğru şekilde karşılamanın doğru yolunu arayacağız.

Herhangi bir dosyanın kapatılması için kullandığımız try-finally'dan bahsedecek olursak. Geçmişe dönük inceleme yaptığımızda bir dosyanın kapatılması konusunda (kapsam içerisinde exception atılma özelliği de dahil) en iyi yol gibi görünüyordu. Çünkü aksi halde finalize() metodu bununla baş edemiyordu. (Detaylı bilgi: finalize() ve Cleaner Kullanmayın! ) Senle try-finally kullanımına yönelik şöyle bir örnek paylaşayım ve üzerinde konuşalım.

// try-finally - No longer the best way to close resources!
static String firstLineOfFile(String path) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader(path));
    try {
        return br.readLine();
    } finally {
        br.close();
    }
}

Fena durmuyor değil mi? Peki kapatılması gereken bir kaynak daha olaya dahil olduğunda ne oluyor?

// try-finally is ugly when used with more than one resource!
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();
    }
}

İç içe girmiş try metodları falan... Biraz ortalık karıştı gibi ne dersin? Aslında kurduğumuz düzenekte mantıksal bir hata söz konusu değil. Bu yöntemin daha önce avantaj olarak bahsettiğimiz exception atılabiliyor olması özelliği try-finally'i tarihe gömmüş ve yerine java 7 ile birlikte gelen try-with-resources'ın geçmesini sağlamıştır. Peki nedir bu olay? Olay tam olarak şu: Sen try içerisinde bir exception alabilirsin örneğin readLine() metod çağrısında dosya içerisinde bir nedenle hata oluşabilir. Bu hata sonucunda tabii ki finally aşaması da dosyayı kapatamayacağından exception atılacaktır. Elimizde iki adet exception var. Temel nedenleri aynı olsada try içerisinde atılan exception, finally içerisinde atılan exception tarafından ezilecektir. Bu ezme işlemi, sorunun nereden kaynaklandığı bilgisini barındıran stack trace'in geliştirici tarafından görüntülenmesini ve kaynak sorunun çözülmesi zorlaştıracaktır.

Bir önceki yazımda da bir işlemin tamamen sonlandırılabilmesi için en garanti yöntemin try-with-resources olduğu spoiler'ını vermiştim. Peki try-with-resources nedir? Nasıl kullanılır? Hemen örnek üzerinden cevaplayayım.

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

Hatta elim değmişken bir önceki örnek gibi ekstra bir tane daha kapatılacak kaynak ekleyeyim sonra cevaplayayım.

// try-with-resources on multiple resources - short and sweet
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);
        }
    }

}

İlk olarak gözüne iç içe try yapısı olmadığı çarpmıştır değil mi? Ne kadar da basit, temiz ve okunaklı görünüyor. Aklıma gelmişken burada bahsedeyim try-with-resources kullanmak istediğiniz durumda sınıfınızı AutoClosable sınıfından kalıtmanız gerekmektedir. İçerisinde bulunan void close() metodu otomatik çağrılıp bahsi geçen kapatma işlemlerini yapmaktadır.

try-with-resources içerisinde ki exception olayından ne haber diye düşündüğünü hisseder gibiyim. try-with-resources yapısında try içerisinde exception atılabileceği gibi AutoClosable sınıfından gelen ve otomatik tetiklenen close() metodu içerisinde de exception atılabilir. Burada try-finally ile bariz fark son atılan exception'ın değil asıl sorunun kaynağına işaret eden ilk exception'ın stack trace'e basılmasıdır. Hatta şöyle bir güzelliğide var ikiden fazla exception ile karşılaşılırsa kaynağı işaret eden hariç diğer tüm exceptionlar susturulur. Bu susturulan exceptionlara Throwable impelement ederek getSuppressed metoduyla erişebilirsin.

Yukarda ki örnekte yer almıyor ama try-with-resources içerisinde aynı try-finally'de olduğu gibi catch mekanizması eklemek mümkündür bkz.

// try-with-resources with a catch clause
static String firstLineOfFile(String path, String defaultVal) {
    try (BufferedReader br = new BufferedReader(new FileReader(path))) {
        return br.readLine();
    } catch (IOException e) {
        return defaultVal;
    }
}

Özetle kapatılmasını, memory leak olmamasını veya exception durumlarında sorun yaşamamasını garantilemek istediğiniz herhangi bir kaynak için java'nın kendi kütüphanelerinde kullanıldığı gibi try-with-resources'ı tercih edin.

Ciao Bella 💃🏻

JavaEffective JavaCreating and Destroying Objectstry-finallytry-with-resourcesInputStreamOutputStream