Sitemap

Java Lambda İfadelerinde Checked Exception Yönetimi

3 min readJan 1, 2024

Yakın zamanlarda okuma imkanı bulduğum güzel bir kitaptan kısa bir bölüm aktarmak istiyorum. Başlangıç ve orta seviyedeki yazılımcılar için kitabın faydalı olacağını düşünüyorum. A Functional Approach to Java kitabını şuradan satın alabilirsiniz. Faydalı olması dileğiyle.

Lambdalara geçmeden önce, Java’daki hatalara(exceptions, istisnalar da diyebiliriz) bakalım. Genel olarak üçe ayrılır.

  • Checked Exceptions(İşaretli Hatalar): Uygulama tarafında yakalanması gereken veya metod tanımlanırken belirtilmesi gereken hatalardır. Bunları ya try-catch ile yakalarız ya da throws ile metod tanımında belirtiriz. Exception sınıfından türerler ve derleme zamanında belli olan hatalardır.
  • Unchecked Exceptions(İşaretsiz Hatalar): Uygulama tanımında belirtilmeyip, JVM tarafından çalışma zamanında yakalanan hatalardır. RuntimeException sınıfından türerler. Yine de try-catch ile yakalayabiliriz.
  • Errors(Yönetilemeyen Hatalar): Throwable sınıfından doğrudan türeyen ve yakalanamayan hatalardır. Genelde JVM’in yeniden başlatılmasına yol açar.

Java’daki hata yönetimi, lambdalardan 18 yıl kadar önce tasarlandığı için Checked Hatalar ile lambdaların kullanımında sıkıntılar olabilir.

Örneğin, bir dosyanın içeriğini java.util.Files.readString() metodu ile okumaya çalışalım. Metodun imzası şuna benzerdir.

public static String readString(Path path) throws IOException {
// ...
}

Metod tanımına bakarsak, IOException ile checked hatanın fırlattığını görürüz. Bu yüzden, metod referans olarak lambda ifadelerinde kullanamayız.

Stream.of(path1, path2, path3)
.map(Files::readString)
.forEach(System.out::println);

Kodu derlemek istersek, hata alırız. Çünkü, readString() metodunu map functional interface içinde kullanmak isteriz ve o da metod tanımında checked hataları belirtmez.

Bariz bir çözüm, try-catch kullanmaktır.

Stream.of(path1, path2, path3)
.map(path -> {
try {
return Files.readString(path);
} catch (IOException e) {
return null;
}
})
.forEach(System.out::println);

Böyle yapınca da, kodda okunurluk çok kötü oldu. Sanki, antipattern bir yapı kullanmış gibi oldu. Bu durumu 3 şekilde çözebiliriz.

  • Güvenli metod ile dışarı çıkarma
  • Checked hataları Unchecked hatalara çevirme
  • Sinsi(Sneaky) throws kullanımı

Güvenli metod ile dışarı çıkarma

try-catch içine aldığımız kod bloğunu farklı bir metod olarak aşağıdaki gibi dışarı çıkarabiliriz. Bu şekilde derleyici hatasından kurtuluruz.

String safeReadString(Path path) {
try {
return Files.readString(path);
} catch (IOException e) {
return null;
}
}
Stream.of(path1, path2, path3)
.map(this::safeReadString)
.filter(Objects::nonNull)
.forEach(System.out::println);

Checked hataları Unchecked hatalara çevirme

Kendi geliştireceğimiz bir arayüzle(veya Spring gibi yardımcı kütüphanelerden gelen) checked hataları, unchecked hatalara çevirebiliriz.

@FunctionalInterface
public interface ThrowingFunction<T, R> extends Function<T, R> {
R applyThrows(T elem) throws Exception;

@Override
default U apply(T t) {
try {
return applyThrows(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
}

public static <T, R> Function<T, R> uncheck(ThrowingFunction<T, R> fn) {
return fn::apply;
}
}

Bu arayüz içinde, applyThrows(T) ile gelen checked hatayı, apply(T) ile unchecked hataya çeviririz. uncheck() static metodundan faydalanırız.

Kullanımına bakalım.

Stream.of(path1, path2, path3)
.map(ThrowingFunction.uncheck(Files::readString))
.filter(Objects::nonNull)
.forEach(System.out::println);

Bu şekilde derleyici hatasından yine kurtuluruz.

Sinsi(Sneaky) throws kullanımı

Sneaky throws deyimi, checked bir hatayı, metodun imzasındaki throws anahtar sözcüğüyle bildirmeden fırlatmak için kullanılan bir hack’tir.

Örnek kullanıma bakalım.

String sneakyRead(File input) {
// ...
if (fileNotFound) {
sneakyThrow(new IOException("File '" + file + "' not found."));
}
// ...
}

Hatanın fırlatılması, sneakyThrow() metoduna ihale edilir. Bunun için Java 8'de gelen Generics ve Exceptions’daki tip çıkarımından faydalanırız. Daha basitçe, throws E gibi generic bir ifade kullanılan metodda, E olan tipi belirten bir üst veya alt sınır yoksa, derleyici E tipini RuntimeException olarak kabul ediyor.

<E extends Throwable> void sneakyThrow(Throwable e) throws E {
throw (E) e;
}

Derleyici bundan şikayet etmese de, çok problemli bir yaklaşımdır. sneakyRead() ile checked hataların yakalanması veya metod tanımında belirtilmesi gereken public sözleşmesini ihlal etmiş olduk. Kodu okuyan için de anlaşılması zor bir yapıdır. En azından return ifadesi ile uygun bir çıkış noktası belirlenebilirdi.

Çok istisna durumlar harici bunu kullanmamakta fayda var.

Yazımızın sonuna geldik. Yeni yılınız kutlu olsun.

--

--

No responses yet