Pengecualian di Java 8 Lambda Expressions

1. Ikhtisar

Di Java 8, Lambda Expressions mulai memfasilitasi pemrograman fungsional dengan menyediakan cara ringkas untuk mengekspresikan perilaku. Namun, Antarmuka Fungsional yang disediakan oleh JDK tidak menangani pengecualian dengan sangat baik - dan kode menjadi bertele-tele dan rumit saat harus menanganinya.

Di artikel ini, kita akan menjelajahi beberapa cara untuk menangani pengecualian saat menulis ekspresi lambda.

2. Menangani Pengecualian yang Tidak Dicentang

Pertama, mari kita pahami masalahnya dengan sebuah contoh.

Kami memiliki Daftar dan kami ingin membagi sebuah konstanta, katakanlah 50 dengan setiap elemen dari daftar ini dan cetak hasilnya:

List integers = Arrays.asList(3, 9, 7, 6, 10, 20); integers.forEach(i -> System.out.println(50 / i));

Ekspresi ini berhasil tetapi ada satu masalah. Jika salah satu elemen dalam daftar adalah 0 , maka kita mendapatkan ArithmeticException: / dengan nol . Mari kita perbaiki dengan menggunakan blok coba-tangkap tradisional sehingga kita mencatat pengecualian seperti itu dan melanjutkan eksekusi untuk elemen berikutnya:

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> { try { System.out.println(50 / i); } catch (ArithmeticException e) { System.err.println( "Arithmetic Exception occured : " + e.getMessage()); } });

Penggunaan coba-tangkap menyelesaikan masalah, tetapi keringkasan Ekspresi Lambda hilang dan tidak lagi menjadi fungsi kecil sebagaimana mestinya.

Untuk mengatasi masalah ini, kita bisa menulis bungkus lambda untuk fungsi lambda . Mari kita lihat kode untuk melihat cara kerjanya:

static Consumer lambdaWrapper(Consumer consumer) { return i -> { try { consumer.accept(i); } catch (ArithmeticException e) { System.err.println( "Arithmetic Exception occured : " + e.getMessage()); } }; }
List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(lambdaWrapper(i -> System.out.println(50 / i)));

Pada awalnya, kami menulis metode pembungkus yang akan bertanggung jawab untuk menangani pengecualian dan kemudian meneruskan ekspresi lambda sebagai parameter untuk metode ini.

Metode pembungkus berfungsi seperti yang diharapkan tetapi, Anda mungkin berpendapat bahwa ini pada dasarnya menghapus blok coba-tangkap dari ekspresi lambda dan memindahkannya ke metode lain dan itu tidak mengurangi jumlah baris kode sebenarnya yang sedang ditulis.

Ini benar dalam kasus ini di mana pembungkusnya khusus untuk kasus penggunaan tertentu tetapi kita dapat menggunakan obat generik untuk meningkatkan metode ini dan menggunakannya untuk berbagai skenario lain:

static  Consumer consumerWrapper(Consumer consumer, Class clazz) { return i -> { try { consumer.accept(i); } catch (Exception ex) { try { E exCast = clazz.cast(ex); System.err.println( "Exception occured : " + exCast.getMessage()); } catch (ClassCastException ccEx) { throw ex; } } }; }
List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach( consumerWrapper( i -> System.out.println(50 / i), ArithmeticException.class));

Seperti yang bisa kita lihat, iterasi metode pembungkus kita ini membutuhkan dua argumen, ekspresi lambda dan tipe Exception yang akan ditangkap. Bungkus lambda ini mampu menangani semua tipe data, tidak hanya Integer , dan menangkap tipe pengecualian tertentu dan bukan Superkelas Exception .

Juga, perhatikan bahwa kami telah mengubah nama metode dari lambdaWrapper menjadi consumerWrapper . Itu karena metode ini hanya menangani ekspresi lambda untuk Antarmuka Fungsional tipe Konsumen . Kita dapat menulis metode pembungkus yang serupa untuk Antarmuka Fungsional lain seperti Fungsi , BiFungsi , BiConsumer , dan sebagainya.

3. Menangani Pengecualian yang Dicentang

Mari memodifikasi contoh dari bagian sebelumnya dan alih-alih mencetak ke konsol, mari menulis ke file.

static void writeToFile(Integer integer) throws IOException { // logic to write to file which throws IOException }

Perhatikan bahwa metode di atas mungkin memunculkan IOException.

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> writeToFile(i));

Saat kompilasi, kami mendapatkan kesalahan:

java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException

Karena IOException adalah pengecualian yang dicentang, kita harus menanganinya secara eksplisit . Kami memiliki dua opsi.

Pertama, kita mungkin hanya membuang pengecualian di luar metode kita dan menanganinya di tempat lain.

Alternatifnya, kita bisa menanganinya di dalam metode yang menggunakan ekspresi lambda.

Mari jelajahi kedua opsi tersebut.

3.1. Melempar Pengecualian yang Dicentang dari Ekspresi Lambda

Mari kita lihat apa yang terjadi ketika kita mendeklarasikan IOException pada metode utama :

public static void main(String[] args) throws IOException { List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> writeToFile(i)); }

Namun, kami mendapatkan kesalahan yang sama dari IOException yang tidak tertangani selama kompilasi .

java.lang.Error: Unresolved compilation problem: Unhandled exception type IOException

Ini karena ekspresi lambda mirip dengan Anonymous Inner Classes.

Dalam kasus kami, metode writeToFile adalah implementasi antarmuka fungsional Konsumen .

Mari kita lihat definisi Konsumen :

@FunctionalInterface public interface Consumer { void accept(T t); }

Seperti yang bisa kita lihat, metode accept tidak mendeklarasikan pengecualian yang dicentang. Inilah mengapa writeToFile tidak diizinkan untuk menampilkan IOException.

Cara paling mudah adalah dengan menggunakan blok coba-tangkap , gabungkan pengecualian yang dicentang menjadi pengecualian yang tidak dicentang dan lontarkan kembali:

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(i -> { try { writeToFile(i); } catch (IOException e) { throw new RuntimeException(e); } }); 

Ini mendapatkan kode untuk dikompilasi dan dijalankan. Namun, pendekatan ini memperkenalkan masalah yang sama yang telah kita bahas di bagian sebelumnya - bertele-tele dan tidak praktis.

Kita bisa menjadi lebih baik dari itu.

Mari buat antarmuka fungsional khusus dengan metode terima tunggal yang memunculkan pengecualian.

@FunctionalInterface public interface ThrowingConsumer { void accept(T t) throws E; }

Dan sekarang, mari terapkan metode pembungkus yang dapat memunculkan kembali pengecualian:

static  Consumer throwingConsumerWrapper( ThrowingConsumer throwingConsumer) { return i -> { try { throwingConsumer.accept(i); } catch (Exception ex) { throw new RuntimeException(ex); } }; }

Terakhir, kami dapat menyederhanakan cara kami menggunakan metode writeToFile :

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(throwingConsumerWrapper(i -> writeToFile(i)));

Ini masih semacam solusi, tetapi hasil akhirnya terlihat cukup bersih dan lebih mudah dirawat .

Keduanya, ThrowingConsumer dan throwingConsumerWrapper adalah generik dan dapat dengan mudah digunakan kembali di berbagai tempat aplikasi kita.

3.2. Handling a Checked Exception in Lambda Expression

In this final section, we'll modify the wrapper to handle checked exceptions.

Since our ThrowingConsumer interface uses generics, we can easily handle any specific exception.

static  Consumer handlingConsumerWrapper( ThrowingConsumer throwingConsumer, Class exceptionClass) { return i -> { try { throwingConsumer.accept(i); } catch (Exception ex) { try { E exCast = exceptionClass.cast(ex); System.err.println( "Exception occured : " + exCast.getMessage()); } catch (ClassCastException ccEx) { throw new RuntimeException(ex); } } }; }

Let's see how to use it in practice:

List integers = Arrays.asList(3, 9, 7, 0, 10, 20); integers.forEach(handlingConsumerWrapper( i -> writeToFile(i), IOException.class));

Note, that the above code handles only IOException, whereas any other kind of exception is rethrown as a RuntimeException .

4. Conclusion

In this article, we showed how to handle a specific exception in lambda expression without losing the conciseness with the help of wrapper methods. We also learned how to write throwing alternatives for the Functional Interfaces present in JDK to either throw or handle a checked exception.

Another way would be to explore the sneaky-throws hack.

Kode sumber lengkap dari Fungsional Antarmuka dan metode pembungkus dapat diunduh dari sini dan kelas uji dari sini, di Github.

Jika Anda mencari solusi kerja out-of-the-box, proyek ThrowingFunction layak untuk dicoba.