Saya punya masalah mencoba ekspresi Lambda dari Java 8. Biasanya berfungsi dengan baik, tapi sekarang saya punya metode yang melempar IOException
. Lebih baik jika Anda melihat kode berikut:
class Bank{
....
public Set<String> getActiveAccountNumbers() throws IOException {
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}
....
}
interface Account{
....
boolean isActive() throws IOException;
String getNumber() throws IOException;
....
}
Masalahnya adalah, itu tidak dikompilasi, karena saya harus menangkap kemungkinan pengecualian dari isActive- dan getNumber-Methods. Tetapi bahkan jika saya secara eksplisit menggunakan try-catch-Block seperti di bawah ini, itu masih tidak dapat dikompilasi karena saya tidak menangkap Exception. Jadi ada bug di JDK, atau saya tidak tahu cara menangkap Pengecualian ini.
class Bank{
....
//Doesn't compile either
public Set<String> getActiveAccountNumbers() throws IOException {
try{
Stream<Account> s = accounts.values().stream();
s = s.filter(a -> a.isActive());
Stream<String> ss = s.map(a -> a.getNumber());
return ss.collect(Collectors.toSet());
}catch(IOException ex){
}
}
....
}
Bagaimana saya bisa membuatnya bekerja? Bisakah seseorang memberi saya petunjuk tentang solusi yang tepat?
java
exception-handling
lambda
java-8
Martin Weber
sumber
sumber
Jawaban:
Anda harus menangkap pengecualian sebelum lolos dari lambda:
Pertimbangkan fakta bahwa lambda tidak dievaluasi di tempat Anda menulisnya, tetapi di beberapa tempat yang sama sekali tidak terkait, dalam kelas JDK. Jadi itu akan menjadi titik di mana pengecualian diperiksa akan dibuang, dan di tempat itu tidak dinyatakan.
Anda dapat mengatasinya dengan menggunakan pembungkus lambda Anda yang menerjemahkan pengecualian yang dicentang ke yang tidak dicentang:
Contoh Anda akan ditulis sebagai
Dalam proyek saya, saya menangani masalah ini tanpa membungkus; alih-alih saya menggunakan metode yang secara efektif meredakan pengecekan pengecualian pada kompiler. Tak perlu dikatakan, ini harus ditangani dengan hati-hati dan semua orang di proyek harus menyadari bahwa pengecualian yang dicentang dapat muncul di tempat itu tidak dinyatakan. Ini adalah kode pipa:
dan Anda bisa berharap untuk
IOException
dilemparkan ke wajah Anda, meskipuncollect
tidak menyatakannya. Dalam sebagian besar, tetapi tidak semua kasus kehidupan nyata Anda ingin hanya memikirkan kembali pengecualian, dan menanganinya sebagai kegagalan umum. Dalam semua kasus itu, tidak ada yang hilang dalam kejelasan atau kebenaran. Berhati-hatilah dengan kasus-kasus lainnya, di mana Anda sebenarnya ingin bereaksi terhadap pengecualian di tempat. Pengembang tidak akan dibuat sadar oleh kompiler bahwa adaIOException
tangkapan di sana dan kompiler sebenarnya akan mengeluh jika Anda mencoba menangkapnya karena kami telah membodohinya dengan meyakini bahwa tidak ada pengecualian yang dapat dilemparkan.sumber
Anda juga dapat menyebarkan rasa sakit statis Anda dengan lambdas, sehingga semuanya terlihat mudah dibaca:
propagate
di sini menerimajava.util.concurrent.Callable
sebagai parameter dan mengonversi pengecualian apa pun yang tertangkap selama panggilan masukRuntimeException
. Ada metode konversi yang serupa dengan Throwables # propagate (Throwable) di Guava.Metode ini tampaknya sangat penting untuk chaining metode lambda, jadi saya berharap suatu hari nanti akan ditambahkan ke salah satu lib populer atau perilaku menyebarkan ini secara default.
sumber
UtilException
Kelas helper ini memungkinkan Anda menggunakan pengecualian yang dicentang di aliran Java, seperti ini:Catatan
Class::forName
lemparanClassNotFoundException
, yang diperiksa . Aliran itu sendiri juga melemparClassNotFoundException
, dan BUKAN beberapa pembungkus pengecualian terkendali.Banyak contoh lain tentang cara menggunakannya (setelah mengimpor secara statis
UtilException
):Tetapi jangan menggunakannya sebelum memahami kelebihan, kekurangan, dan batasan berikut :
• Jika kode panggilan adalah untuk menangani pengecualian yang diperiksa, Anda HARUS menambahkannya ke klausa pelemparan dari metode yang berisi aliran. Kompiler tidak akan memaksa Anda untuk menambahkannya lagi, jadi lebih mudah untuk melupakannya.
• Jika kode panggilan sudah menangani pengecualian yang dicentang, kompiler AKAN mengingatkan Anda untuk menambahkan klausa pelemparan ke deklarasi metode yang berisi aliran (jika Anda tidak melakukannya akan mengatakan: Pengecualian tidak pernah dilemparkan ke badan pernyataan percobaan yang sesuai ).
• Dalam kasus apa pun, Anda tidak akan dapat mengelilingi aliran itu sendiri untuk menangkap pengecualian yang dicentang DI DALAM metode yang berisi aliran (jika Anda mencoba, kompiler akan mengatakan: Pengecualian tidak pernah dilemparkan ke badan pernyataan percobaan yang sesuai).
• Jika Anda memanggil metode yang benar-benar tidak pernah dapat membuang pengecualian yang dinyatakannya, maka Anda tidak boleh memasukkan klausa pelemparan. Sebagai contoh: String baru (byteArr, "UTF-8") melempar UnsupportedEncodingException, tetapi UTF-8 dijamin oleh spesifikasi Java untuk selalu hadir. Di sini, deklarasi melempar adalah gangguan dan solusi apa pun untuk membungkamnya dengan boilerplate minimal disambut.
• Jika Anda membenci pengecualian yang dicentang dan merasa mereka tidak boleh ditambahkan ke bahasa Java untuk memulai (semakin banyak orang berpikir seperti ini, dan saya BUKAN salah satu dari mereka), maka jangan tambahkan pengecualian yang dicentang ke melempar klausa metode yang berisi aliran. Pengecualian yang dicentang akan, kemudian, berperilaku seperti pengecualian yang tidak dicentang.
• Jika Anda menerapkan antarmuka yang ketat di mana Anda tidak memiliki opsi untuk menambahkan deklarasi lemparan, namun melempar pengecualian sepenuhnya tepat, kemudian membungkus pengecualian hanya untuk mendapatkan hak istimewa melemparkannya menghasilkan stacktrace dengan pengecualian palsu. yang tidak berkontribusi informasi tentang apa yang sebenarnya salah. Contoh yang baik adalah Runnable.run (), yang tidak membuang pengecualian yang dicentang. Dalam hal ini, Anda dapat memutuskan untuk tidak menambahkan pengecualian yang dicentang ke klausa melempar dari metode yang berisi aliran.
• Dalam hal apa pun, jika Anda memutuskan untuk TIDAK menambahkan (atau lupa menambahkan) pengecualian yang dicentang untuk klausa melempar dari metode yang berisi aliran, perhatikan 2 konsekuensi dari melemparkan pengecualian yang DIPERIKSA:
1) Kode panggilan tidak akan dapat menangkapnya dengan nama (jika Anda mencoba, kompiler akan mengatakan: Pengecualian tidak pernah dilemparkan ke badan pernyataan percobaan yang sesuai). Ini akan menggelembung dan mungkin ditangkap dalam loop program utama oleh "catch Exception" atau "catch Throwable", yang mungkin merupakan apa yang Anda inginkan.
2) Ini melanggar prinsip paling tidak mengejutkan: itu tidak akan lagi cukup untuk menangkap RuntimeException untuk dapat menjamin menangkap semua pengecualian yang mungkin. Untuk alasan ini, saya percaya ini tidak boleh dilakukan dalam kode kerangka kerja, tetapi hanya dalam kode bisnis yang Anda kontrol penuh.
Kesimpulannya: Saya percaya batasan di sini tidak serius, dan
UtilException
kelas dapat digunakan tanpa rasa takut. Namun, itu terserah Anda!sumber
Anda dapat berpotensi memutar
Stream
varian Anda sendiri dengan membungkus lambda Anda untuk melemparkan pengecualian yang tidak dicentang dan kemudian membuka bungkus pengecualian yang tidak dicentang pada operasi terminal:Maka Anda bisa menggunakan ini dengan cara yang sama persis seperti
Stream
:Solusi ini akan membutuhkan sedikit boilerplate, jadi saya sarankan Anda melihat perpustakaan yang sudah saya buat yang melakukan apa yang saya jelaskan di sini untuk seluruh
Stream
kelas (dan banyak lagi!).sumber
new StreamBridge<>(ts, IOException.class);
->new StreamBridge<>(s, IOException.class);
StreamAdapter
.Gunakan metode #propagate (). Contoh implementasi non-Jambu Biji dari Java 8 Blog oleh Sam Beran :
sumber
Ini tidak langsung menjawab pertanyaan (ada banyak jawaban lain yang melakukannya) tetapi mencoba menghindari masalah sejak awal:
Dalam pengalaman saya, kebutuhan untuk menangani pengecualian dalam
Stream
(atau ekspresi lambda lainnya) sering kali berasal dari fakta bahwa pengecualian tersebut dinyatakan dilemparkan dari metode di mana mereka tidak boleh dibuang. Ini sering datang dari pencampuran logika bisnis dengan input dan output.Account
Antarmuka Anda adalah contoh sempurna:Alih-alih melempar
IOException
pada setiap rajin, pertimbangkan desain ini:Metode ini
AccountReader.readAccount(…)
dapat membaca akun dari database atau file atau apa pun dan melemparkan pengecualian jika tidak berhasil. Itu membangun sebuahAccount
objek yang sudah berisi semua nilai, siap digunakan. Karena nilai telah dimuat olehreadAccount(…)
, getter tidak akan membuang pengecualian. Dengan demikian Anda dapat menggunakannya secara bebas di lambdas tanpa perlu membungkus, menutupi atau menyembunyikan pengecualian.Tentu saja tidak selalu mungkin untuk melakukannya dengan cara yang saya jelaskan, tetapi sering kali itu dan itu mengarah ke kode pembersih sama sekali (IMHO):
throws IOException
tanpa digunakan selain untuk memuaskan kompilerAccount
kekal dan mendapat keuntungan dari keuntungannya (mis. Keamanan benang)Account
dalam lambdas (misalnya dalam aStream
)sumber
Ini dapat diatasi dengan kode sederhana di bawah ini dengan Stream dan Coba di AbacusUtil :
Pengungkapan : Saya pengembang
AbacusUtil
.sumber
Memperluas solusi @marcg, Anda biasanya dapat melempar dan menangkap pengecualian yang dicentang di Streams; yaitu, kompiler akan meminta Anda untuk menangkap / melempar kembali seperti Anda berada di luar aliran !!
Kemudian, contoh Anda akan ditulis sebagai berikut (menambahkan tes untuk menunjukkannya dengan lebih jelas):
sumber
Untuk menambahkan kode penanganan IOException (ke RuntimeException) dengan benar, metode Anda akan terlihat seperti ini:
Masalahnya sekarang adalah bahwa
IOException
harus ditangkap sebagaiRuntimeException
dan dikonversi kembali keIOException
- dan itu akan menambahkan lebih banyak kode ke metode di atas.Mengapa digunakan
Stream
ketika itu bisa dilakukan seperti ini - dan metode ini melemparIOException
sehingga tidak ada kode tambahan yang diperlukan untuk itu juga:sumber
Dengan mengingat masalah ini, saya mengembangkan perpustakaan kecil untuk menangani pengecualian dan lambda yang diperiksa. Adaptor khusus memungkinkan Anda untuk berintegrasi dengan jenis fungsional yang ada:
https://github.com/TouK/ThrowingFunction/
sumber
Contoh Anda dapat ditulis sebagai:
Kelas Unthrow dapat diambil di sini https://github.com/SeregaLBN/StreamUnthrower
sumber
Jika Anda tidak keberatan menggunakan perpustakaan pihak ke-3, lib -reaksi lib AOL , pengungkapan :: Saya adalah kontributor, memiliki kelas ExceptionSoftener yang dapat membantu di sini.
sumber
Antarmuka fungsional di Java tidak menyatakan pengecualian yang dicentang atau tidak dicentang. Kita perlu mengubah tanda tangan metode dari:
Untuk:
Atau tangani dengan blok try-catch:
Pilihan lain adalah menulis pembungkus khusus atau menggunakan perpustakaan seperti ThrowingFunction. Dengan perpustakaan, kita hanya perlu menambahkan dependensi ke pom.xml kita:
Dan gunakan kelas spesifik seperti ThrowingFunction, ThrowingConsumer, ThrowingPredicate, ThrowingRunnable, ThrowingSupplier.
Pada akhirnya kode terlihat seperti ini:
sumber
Saya tidak melihat cara penanganan pengecualian diperiksa dalam aliran (Java -8), satu-satunya cara saya diterapkan adalah menangkap pengecualian diperiksa dalam aliran dan melemparkan kembali mereka sebagai pengecualian tidak dicentang.
sumber
Jika Anda ingin menangani pengecualian dalam aliran dan terus memproses yang tambahan, ada artikel bagus di DZone oleh Brian Vermeer menggunakan konsep Either. Ini menunjukkan cara terbaik untuk menangani situasi ini. Satu-satunya hal yang hilang adalah kode sampel. Ini adalah contoh dari penjelajahan saya menggunakan konsep-konsep dari artikel itu.
sumber