Bagaimana saya bisa membuang pengecualian CHECKED dari dalam Java 8 stream / lambdas?
Dengan kata lain, saya ingin membuat kode seperti kompilasi ini:
public List<Class> getClasses() throws ClassNotFoundException {
List<Class> classes =
Stream.of("java.lang.Object", "java.lang.Integer", "java.lang.String")
.map(className -> Class.forName(className))
.collect(Collectors.toList());
return classes;
}
Kode ini tidak dikompilasi, karena Class.forName()
metode di atas melempar ClassNotFoundException
, yang diperiksa.
Harap dicatat bahwa saya TIDAK ingin membungkus pengecualian yang diperiksa di dalam pengecualian runtime dan membuang pengecualian yang tidak dicentang yang dibungkus. Saya ingin melempar pengecualian yang dicentang itu sendiri , dan tanpa menambahkan jelek try
/ catches
ke aliran.
throws ClassNotFoundException
dalam deklarasi metode yang berisi aliran, sehingga kode panggilan akan mengharapkan dan diizinkan untuk menangkap pengecualian yang diperiksa.Jawaban:
Jawaban sederhana untuk pertanyaan Anda adalah: Anda tidak bisa, setidaknya tidak secara langsung. Dan itu bukan salahmu. Oracle mengacaukannya. Mereka berpegang teguh pada konsep pengecualian yang diperiksa, tetapi secara tidak konsisten lupa untuk mengurus pengecualian yang diperiksa ketika mendesain antarmuka fungsional, stream, lambda, dll. Itu semua bergantung pada banyak pakar seperti Robert C. Martin yang menyebut pengecualian yang dicek sebagai percobaan yang gagal.
Menurut pendapat saya, ini adalah bug besar di API dan bug kecil dalam spesifikasi bahasa .
Bug dalam API adalah bahwa ia tidak menyediakan fasilitas untuk meneruskan pengecualian yang diperiksa di mana ini sebenarnya akan membuat banyak akal untuk pemrograman fungsional. Seperti yang akan saya tunjukkan di bawah ini, fasilitas seperti itu akan mudah dilakukan.
Bug dalam spesifikasi bahasa adalah bahwa ia tidak mengizinkan parameter tipe untuk menyimpulkan daftar tipe alih-alih tipe tunggal asalkan parameter tipe hanya digunakan dalam situasi di mana daftar tipe diperbolehkan (
throws
klausa).Harapan kami sebagai programmer Java adalah bahwa kode berikut harus dikompilasi:
Namun, itu memberi:
Cara di mana antarmuka fungsional didefinisikan saat ini mencegah Compiler dari meneruskan pengecualian - tidak ada deklarasi yang akan mengatakan
Stream.map()
bahwa jikaFunction.apply() throws E
,Stream.map() throws E
juga.Apa yang hilang adalah deklarasi parameter tipe untuk melewati pengecualian yang diperiksa. Kode berikut menunjukkan bagaimana parameter tipe pass-through sebenarnya bisa dideklarasikan dengan sintaks saat ini. Kecuali untuk kasus khusus pada baris yang ditandai, yang merupakan batas yang dibahas di bawah ini, kode ini mengkompilasi dan berperilaku seperti yang diharapkan.
Dalam kasus
throwSomeMore
kami ingin melihatIOException
yang terlewatkan, tetapi sebenarnya merindukanException
.Ini tidak sempurna karena inferensi tipe tampaknya mencari tipe tunggal, bahkan dalam kasus pengecualian. Karena jenis inferensi membutuhkan satu jenis,
E
perlu tekad untuk umumsuper
dariClassNotFoundException
danIOException
, yangException
.Tweak untuk definisi inferensi tipe diperlukan sehingga kompiler akan mencari beberapa tipe jika parameter tipe digunakan di mana daftar tipe diperbolehkan (
throws
klausa). Kemudian tipe eksepsi yang dilaporkan oleh kompiler akan sama spesifiknya denganthrows
deklarasi asli dari pengecualian yang diperiksa dari metode yang direferensikan, bukan tipe super catch-all tunggal.Berita buruknya adalah ini berarti Oracle mengacaukannya. Tentu saja mereka tidak akan merusak kode pengguna-lahan, tetapi memperkenalkan parameter tipe pengecualian ke antarmuka fungsional yang ada akan merusak kompilasi semua kode pengguna-lahan yang menggunakan antarmuka ini secara eksplisit. Mereka harus menemukan beberapa gula sintaksis baru untuk memperbaikinya.
Berita yang lebih buruk adalah bahwa topik ini sudah dibahas oleh Brian Goetz pada tahun 2010 https://blogs.oracle.com/briangoetz/entry/exception_transparency_in_java (tautan baru: http://mail.openjdk.java.net/pipermail/lambda -dev / 2010-Juni / 001484.html ) tetapi saya diberitahu bahwa penyelidikan ini pada akhirnya tidak berjalan dengan baik, dan bahwa tidak ada pekerjaan saat ini di Oracle yang saya tahu untuk mengurangi interaksi antara pengecualian yang dicek dan lambdas.
sumber
try-catch
blok di dalam lambda, dan itu sama sekali tidak masuk akal. BegituClass.forName
digunakan dalam beberapa cara di lambda, misalnya dalamnames.forEach(Class::forName)
, masalahnya ada di sana. Pada dasarnya, metode yang membuang pengecualian diperiksa telah dikeluarkan dari berpartisipasi dalam pemrograman fungsional sebagai antarmuka fungsional secara langsung, oleh (miskin!) Desain.LambdaExceptionUtil
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
LambdaExceptionUtil
):CATATAN 1: The
rethrow
metode dariLambdaExceptionUtil
kelas di atas dapat digunakan tanpa rasa takut, dan OK untuk digunakan dalam situasi apa pun . Terima kasih banyak kepada pengguna @PaoloC yang membantu memecahkan masalah terakhir: Sekarang kompiler akan meminta Anda untuk menambahkan klausa melempar dan semuanya seolah-olah Anda bisa melempar pengecualian yang diperiksa secara native di stream Java 8.CATATAN 2: The
uncheck
metode dariLambdaExceptionUtil
kelas atas adalah metode bonus, dan dapat dengan aman menghapus mereka dari kelas jika Anda tidak ingin menggunakannya. Jika Anda menggunakannya, lakukan dengan hati-hati, dan jangan sebelum memahami kasus penggunaan berikut, kelebihan / kekurangan dan keterbatasan:• Anda dapat menggunakan
uncheck
metode jika Anda memanggil metode yang benar-benar tidak pernah dapat membuang pengecualian yang dinyatakannya. 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 platplate minimal disambut:String text = uncheck(() -> new String(byteArr, "UTF-8"));
• Anda dapat menggunakan
uncheck
metode jika Anda menerapkan antarmuka yang ketat di mana Anda tidak memiliki opsi untuk menambahkan deklarasi lemparan, namun melemparkan pengecualian sepenuhnya sesuai. Membungkus pengecualian hanya untuk mendapatkan hak istimewa dengan 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 kasus apa pun, jika Anda memutuskan untuk menggunakan
uncheck
metode ini, waspadalah terhadap 2 konsekuensi membuang pengecualian yang DIPERIKSA tanpa klausa pelemparan: 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 terperangkap 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 menangkapRuntimeException
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 sepenuhnya Anda kontrol.sumber
@SuppressWarnings ("unchecked")
tipuan kompiler benar-benar tidak dapat diterima.Anda tidak dapat melakukan ini dengan aman. Anda dapat menipu, tetapi kemudian program Anda rusak dan ini pasti akan kembali menggigit seseorang (seharusnya Anda, tetapi sering kali kecurangan kami meledak pada orang lain.)
Ini cara yang sedikit lebih aman untuk melakukannya (tapi saya masih tidak merekomendasikan ini.)
Di sini, yang Anda lakukan adalah menangkap pengecualian di lambda, membuang sinyal keluar dari pipa aliran yang menunjukkan bahwa perhitungan gagal secara luar biasa, menangkap sinyal, dan bertindak pada sinyal itu untuk melempar pengecualian yang mendasarinya. Kuncinya adalah bahwa Anda selalu menangkap pengecualian sintetis, daripada membiarkan pengecualian yang diperiksa bocor tanpa menyatakan bahwa pengecualian dilemparkan.
sumber
Function
dll tidak melakukanthrows
apa pun; Saya hanya penasaran.throw w.cause;
tidak akan membuat kompiler mengeluh bahwa metode ini tidak melempar atau menangkapThrowable
? Jadi, kemungkinan para pemainIOException
akan dibutuhkan di sana. Lebih lanjut, jika lambda melempar lebih dari satu jenis pengecualian yang diperiksa, badan tangkapan akan menjadi agak jelek dengan beberapainstanceof
cek (atau sesuatu yang lain dengan tujuan yang sama) untuk memverifikasi pengecualian yang dicentang dibuang.Kamu bisa!
Memperluas @marcg
UtilException
dan menambahkanthrow E
jika perlu: dengan cara ini, kompiler akan meminta Anda untuk menambahkan klausa melempar dan semuanya seolah-olah Anda bisa melempar pengecualian yang diperiksa secara native pada stream java 8.Petunjuk: cukup salin / tempel
LambdaExceptionUtil
di IDE Anda dan kemudian gunakan seperti yang ditunjukkan di bawah iniLambdaExceptionUtilTest
.Beberapa tes untuk menunjukkan penggunaan dan perilaku:
sumber
<Integer>
sebelumnyamap
. Bahkan, kompiler java tidak dapat menyimpulkanInteger
tipe pengembalian. Yang lainnya harus benar.Exception
.Cukup gunakan salah satu dari NoException (proyek saya), jOOλ's Unchecked , throw -lambdas , Throwable interface , atau Faux Pas .
sumber
Saya menulis pustaka yang memperluas Stream API untuk memungkinkan Anda membuang pengecualian yang diperiksa. Ini menggunakan trik Brian Goetz.
Kode Anda akan menjadi
sumber
Jawaban ini mirip dengan 17 tetapi menghindari definisi pengecualian pembungkus:
sumber
Kamu tidak bisa.
Namun, Anda mungkin ingin melihat salah satu proyek saya yang memungkinkan Anda untuk lebih mudah memanipulasi "melempar lambda" tersebut.
Dalam kasus Anda, Anda dapat melakukan itu:
dan menangkap
MyException
.Itu salah satu contohnya. Contoh lain adalah bahwa Anda dapat
.orReturn()
beberapa nilai default.Perhatikan bahwa ini MASIH sedang dalam proses, masih banyak yang akan datang. Nama yang lebih baik, lebih banyak fitur, dll.
sumber
.orThrowChecked()
metode untuk proyek Anda yang memungkinkan pengecualian yang dicentang itu sendiri dibuang. . Silakan lihatUtilException
jawaban saya di halaman ini, dan lihat apakah Anda menyukai gagasan untuk menambahkan kemungkinan ketiga ini ke proyek Anda.Stream
implementasinyaAutoCloseable
?MyException
atas Anda perlu pengecualian yang tidak dicentang?Meringkas komentar di atas solusi lanjutan adalah dengan menggunakan pembungkus khusus untuk fungsi yang tidak diperiksa dengan pembangun seperti API yang menyediakan pemulihan, rethrowing dan suppresing.
Kode di bawah ini menunjukkannya untuk antarmuka Konsumen, Pemasok dan Fungsi. Ini dapat diperluas dengan mudah. Beberapa kata kunci publik telah dihapus untuk contoh ini.
Kelas Coba adalah titik akhir untuk kode klien. Metode aman mungkin memiliki nama unik untuk setiap jenis fungsi. CheckedConsumer , CheckedSupplier dan CheckedFunction diperiksa analog fungsi lib yang dapat digunakan secara independen dari Try
CheckedBuilder adalah antarmuka untuk menangani pengecualian dalam beberapa fungsi yang diperiksa. orTry memungkinkan menjalankan fungsi tipe lain yang sama jika sebelumnya gagal. pegangan menyediakan penanganan pengecualian termasuk pemfilteran jenis pengecualian. Urutan penangan penting. Kurangi metode yang tidak aman dan rethrow rethrow pengecualian terakhir dalam rantai eksekusi. Kurangi metode orElse dan orElseGet kembalikan nilai alternatif seperti yang opsional jika semua fungsi gagal. Juga ada metode suppress . CheckedWrapper adalah implementasi umum dari CheckedBuilder.
sumber
TL; DR Cukup gunakan Lombok
@SneakyThrows
.Christian Hujer telah menjelaskan secara terperinci mengapa melemparkan pengecualian yang dicek dari sungai, sebenarnya, tidak mungkin karena keterbatasan Jawa.
Beberapa jawaban lain telah menjelaskan trik untuk mengatasi keterbatasan bahasa tetapi masih dapat memenuhi persyaratan melempar "pengecualian yang dicek itu sendiri, dan tanpa menambahkan mencoba / menangkap jelek ke aliran" , beberapa dari mereka membutuhkan puluhan baris tambahan dari boilerplate.
Saya akan menyoroti opsi lain untuk melakukan ini yang IMHO jauh lebih bersih daripada yang lain: Lombok
@SneakyThrows
. Telah disebutkan lewat jawaban lain tetapi sedikit terkubur di bawah banyak detail yang tidak perlu.Kode yang dihasilkan sangat sederhana:
Kami hanya perlu satu
Extract Method
refactoring (dilakukan oleh IDE) dan satu baris tambahan untuk@SneakyThrows
. Anotasi ini dengan hati-hati menambahkan semua pelat untuk memastikan bahwa Anda dapat membuang pengecualian yang sudah diperiksa tanpa membungkusnya dalamRuntimeException
dan tanpa harus mendeklarasikannya secara eksplisit.sumber
Anda juga dapat menulis metode pembungkus untuk membungkus pengecualian yang tidak dicentang, dan bahkan meningkatkan pembungkus dengan parameter tambahan yang mewakili antarmuka fungsional lain (dengan tipe pengembalian R yang sama ). Dalam hal ini Anda dapat melewati fungsi yang akan dieksekusi dan dikembalikan jika ada pengecualian. Lihat contoh di bawah ini:
sumber
Berikut ini pandangan atau solusi yang berbeda untuk masalah aslinya. Di sini saya menunjukkan bahwa kami memiliki opsi untuk menulis kode yang hanya akan memproses subset nilai yang valid dengan opsi untuk mendeteksi dan menangani kasus ketika pengecualian dilemparkan.
sumber
Saya setuju dengan komentar di atas, dalam menggunakan Stream.map Anda terbatas pada mengimplementasikan Function yang tidak melempar Pengecualian.
Namun Anda dapat membuat FungsionalInterface Anda sendiri yang melempar seperti di bawah ini ..
kemudian implementasikan menggunakan Lambdas atau referensi seperti yang ditunjukkan di bawah ini.
sumber
Satu-satunya cara built-in untuk menangani pengecualian yang diperiksa yang dapat dibuang oleh suatu
map
operasi adalah dengan merangkumnya dalam aCompletableFuture
. (SebuahOptional
adalah alternatif yang lebih sederhana jika Anda tidak perlu menyimpan pengecualian.) Kelas-kelas ini dimaksudkan untuk memungkinkan Anda untuk mewakili operasi kontinjensi secara fungsional.Diperlukan beberapa metode pembantu non-sepele, tetapi Anda dapat menemukan kode yang relatif singkat, sambil tetap menunjukkan bahwa hasil aliran Anda bergantung pada
map
operasi yang telah selesai dengan sukses. Begini tampilannya:Ini menghasilkan output berikut:
The
applyOrDie
Metode mengambilFunction
yang melempar pengecualian, dan bertobat menjadiFunction
yang kembali yang sudah-selesaiCompletableFuture
- baik diselesaikan secara normal dengan hasil fungsi asli, atau menyelesaikan sangat dengan pengecualian dilemparkan.map
Operasi kedua menggambarkan bahwa Anda sekarang telah mendapatkanStream<CompletableFuture<T>>
alih - alih hanyaStream<T>
.CompletableFuture
menangani hanya menjalankan operasi ini jika operasi hulu berhasil. API membuat ledakan ini, tetapi relatif tidak menyakitkan.Sampai Anda mencapai
collect
fase, itu. Di sinilah kami membutuhkan metode pembantu yang cukup signifikan. Kami ingin "mengangkat" operasi koleksi normal (dalam hal ini,toList()
) "di dalam"CompletableFuture
-cfCollector()
memungkinkan kita melakukan itu menggunakansupplier
,accumulator
,combiner
, danfinisher
yang tidak perlu tahu apa-apa tentangCompletableFuture
.Metode pembantu dapat ditemukan di GitHub di
MonadUtils
kelas saya , yang masih banyak pekerjaan yang sedang berjalan.sumber
Mungkin, cara yang lebih baik dan lebih fungsional adalah dengan membungkus pengecualian dan menyebarkannya lebih jauh dalam aliran. Lihatlah jenis Coba Vavr misalnya.
Contoh:
ATAU
Implementasi ke-2 menghindari pembungkus pengecualian dalam a
RuntimeException
.throwUnchecked
berfungsi karena hampir selalu semua pengecualian umum diperlakukan sebagai tidak dicentang di java.sumber
Saya menggunakan jenis pembungkus ini:
Ini akan membutuhkan penanganan pengecualian ini secara statis:
Cobalah online!
Meskipun pengecualian akan tetap dilemparkan kembali selama
rethrow()
panggilan pertama (oh, Java generics ...), cara ini memungkinkan untuk mendapatkan definisi statis yang ketat tentang kemungkinan pengecualian (harus menyatakannyathrows
). Dan tidakinstanceof
diperlukan sesuatu.sumber
Saya pikir pendekatan ini adalah yang benar:
Membungkus pengecualian yang diperiksa di
Callable
dalam aUndeclaredThrowableException
(itulah use case untuk pengecualian ini) dan membuka bungkusnya di luar.Ya, saya merasa jelek, dan saya akan menyarankan untuk tidak menggunakan lambdas dalam kasus ini dan kembali ke loop lama yang baik, kecuali jika Anda bekerja dengan aliran paralel dan paralellisasi membawa manfaat obyektif yang membenarkan keterbacaan kode.
Seperti yang telah ditunjukkan oleh banyak orang lain, ada solusi untuk situasi ini, dan saya harap salah satu dari mereka akan menjadikannya versi Java di masa depan.
sumber