Saya menghargai banyak fitur Java 8 baru tentang lambdas dan antarmuka metode standar. Namun, saya masih bosan dengan pengecualian yang diperiksa. Misalnya, jika saya hanya ingin membuat daftar semua bidang yang terlihat dari suatu objek, saya hanya ingin menulis ini:
Arrays.asList(p.getClass().getFields()).forEach(
f -> System.out.println(f.get(p))
);
Namun, karena get
metode ini dapat membuang pengecualian yang diperiksa, yang tidak setuju dengan Consumer
kontrak antarmuka, maka saya harus menangkap pengecualian itu dan menulis kode berikut:
Arrays.asList(p.getClass().getFields()).forEach(
f -> {
try {
System.out.println(f.get(p));
} catch (IllegalArgumentException | IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
);
Namun dalam kebanyakan kasus saya hanya ingin pengecualian untuk dilemparkan sebagai RuntimeException
dan biarkan program menangani, atau tidak, pengecualian tanpa kesalahan kompilasi.
Jadi, saya ingin pendapat Anda tentang solusi kontroversial saya untuk gangguan pengecualian yang diperiksa. Untuk itu, saya membuat antarmuka tambahan ConsumerCheckException<T>
dan fungsi utilitas rethrow
( diperbarui sesuai dengan saran komentar Doval ) sebagai berikut:
@FunctionalInterface
public interface ConsumerCheckException<T>{
void accept(T elem) throws Exception;
}
public class Wrappers {
public static <T> Consumer<T> rethrow(ConsumerCheckException<T> c) {
return elem -> {
try {
c.accept(elem);
} catch (Exception ex) {
/**
* within sneakyThrow() we cast to the parameterized type T.
* In this case that type is RuntimeException.
* At runtime, however, the generic types have been erased, so
* that there is no T type anymore to cast to, so the cast
* disappears.
*/
Wrappers.<RuntimeException>sneakyThrow(ex);
}
};
}
/**
* Reinier Zwitserloot who, as far as I know, had the first mention of this
* technique in 2009 on the java posse mailing list.
* http://www.mail-archive.com/[email protected]/msg05984.html
*/
public static <T extends Throwable> T sneakyThrow(Throwable t) {
throw (T) t;
}
}
Dan sekarang saya bisa menulis:
Arrays.asList(p.getClass().getFields()).forEach(
rethrow(f -> System.out.println(f.get(p)))
);
Saya tidak yakin bahwa ini adalah ungkapan terbaik untuk membalikkan pengecualian yang diperiksa, tetapi seperti yang saya jelaskan, saya ingin memiliki cara yang lebih nyaman untuk mencapai contoh pertama saya tanpa berurusan dengan pengecualian yang diperiksa dan ini adalah cara yang lebih sederhana yang saya temukan untuk melakukannya.
sumber
sneakyThrow
bagian dalamrethrow
untuk membuang yang asli, memeriksa pengecualian alih-alih membungkusnya dalamRuntimeException
. Atau Anda dapat menggunakan@SneakyThrows
anotasi dari Project Lombok yang melakukan hal yang sama.Consumer
s dalamforEach
dapat dieksekusi secara paralel saat menggunakan paralelStream
s. Suatu throwable yang muncul dari dalam konsumen kemudian akan menyebar ke thread panggilan, yang 1) tidak akan menghentikan konsumen yang berjalan bersamaan lainnya, yang mungkin atau mungkin tidak sesuai, dan 2) jika lebih dari satu konsumen melempar sesuatu, hanya salah satu throwables akan terlihat oleh utas panggilan.Jawaban:
Keuntungan, kerugian, dan keterbatasan teknik Anda:
Jika kode panggilan untuk menangani pengecualian yang dicentang Anda HARUS menambahkannya ke klausa pelemparan dari metode yang berisi aliran. Kompiler tidak akan memaksa Anda untuk menambahkannya lagi, jadi lebih mudah untuk melupakannya. Sebagai contoh:
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 hal 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 secara harfiah 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 platplate minimal dipersilahkan.
Jika Anda membenci pengecualian yang dicentang dan merasa mereka tidak boleh ditambahkan ke bahasa Jawa untuk memulai (semakin banyak orang berpikir seperti ini, dan saya BUKAN salah satu dari mereka), maka jangan tambahkan pengecualian yang dicentang ke lemparan klausa metode yang berisi aliran. Pengecualian yang dicentang akan, 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 sesuai, maka membungkus pengecualian hanya untuk mendapatkan hak istimewa melemparkannya menghasilkan stacktrace dengan pengecualian palsu yang berkontribusi tidak ada 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 kasus 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:
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.
Itu 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 sepenuhnya Anda kontrol.
Referensi:
CATATAN: Jika Anda memutuskan untuk menggunakan teknik ini, Anda dapat menyalin
LambdaExceptionUtil
kelas helper dari StackOverflow: https://stackoverflow.com/questions/27644361/how-can-i-throw-checked-exceptions-from-inside-java-8 -streams . Ini memberi Anda implementasi lengkap (Fungsi, Konsumen, Pemasok ...), dengan contoh.sumber
Dalam contoh ini, bisakah itu benar-benar gagal? Jangan berpikir begitu, tapi mungkin kasus Anda spesial. Jika itu benar-benar "tidak bisa gagal", dan itu hanya kompiler yang menjengkelkan, saya suka membungkus pengecualian dan melemparkan
Error
dengan komentar "tidak dapat terjadi". Menjadikan semuanya sangat jelas untuk pemeliharaan. Kalau tidak, mereka akan bertanya-tanya "bagaimana ini bisa terjadi" dan "siapa yang menangani ini?"Ini dalam praktik yang kontroversial jadi YMMV. Saya mungkin akan mendapatkan beberapa downvotes.
sumber
clone
jenisFoo
yang disegel yang diketahui mendukungnya, dan ia melemparCloneNotSupportedException
. Bagaimana itu bisa terjadi kecuali kode terhubung dengan beberapa jenis yang tidak terduga lainnyaFoo
? Dan jika itu terjadi, adakah yang bisa dipercaya?An Error is a subclass of Throwable that indicates serious problems that a reasonable application should not try to catch.
(dikutip dari Javadoc ofError
) Ini adalah situasi yang persis seperti itu, karena itu jauh dari tidak memadai, melempar diError
sini adalah pilihan yang paling tepat.versi lain dari kode ini di mana pengecualian yang diperiksa hanya ditunda:
keajaibannya adalah jika Anda menelepon:
maka kode Anda wajib menangkap pengecualian
sumber
sneakyThrow itu keren! Aku benar-benar merasakan sakitmu.
Jika Anda harus tinggal di Jawa ...
Paguro memiliki antarmuka fungsional yang membungkus pengecualian yang diperiksa sehingga Anda tidak perlu memikirkan hal ini lagi. Ini mencakup koleksi yang tidak dapat diubah dan transformasi fungsional di sepanjang garis transduser Clojure (atau Java 8 Streams) yang mengambil antarmuka fungsional dan membungkus pengecualian yang diperiksa.
Ada juga VAVr dan The Eclipse Collections untuk dicoba.
Kalau tidak, gunakan Kotlin
Kotlin kompatibel 2 arah dengan Java, tidak memiliki pengecualian yang diperiksa, tidak ada primitif (well, bukan programmer yang harus memikirkan), sistem tipe yang lebih baik, mengasumsikan imutabilitas, dll. Meskipun saya membuat perpustakaan Paguro di atas, saya ' m mengkonversi semua kode Java yang saya bisa ke Kotlin. Apa pun yang saya lakukan di Jawa sekarang saya lebih suka melakukannya di Kotlin.
sumber
Pengecualian yang diperiksa sangat berguna dan penanganannya tergantung pada jenis produk yang Anda kode bagian dari:
Biasanya pustaka tidak boleh menangani pengecualian yang dicentang, tetapi menyatakan sebagai dilemparkan oleh API publik. Kasus yang jarang terjadi ketika mereka dapat dibungkus dengan RuntimeExcepton biasa adalah, misalnya, membuat di dalam kode perpustakaan beberapa dokumen xml pribadi dan menguraikannya, membuat beberapa file sementara dan kemudian membacanya.
Untuk aplikasi desktop, Anda harus memulai pengembangan produk dengan membuat kerangka kerja penanganan pengecualian Anda sendiri. Menggunakan kerangka kerja Anda sendiri biasanya Anda akan membungkus pengecualian yang diperiksa menjadi beberapa dari dua hingga empat pembungkus (subkelas kustom RuntimeExcepion) dan menanganinya dengan satu Pengecualian.
Untuk server biasanya kode Anda akan berjalan di dalam kerangka kerja tertentu. Jika Anda tidak menggunakan (server kosong) buat kerangka kerja Anda sendiri yang serupa (berdasarkan Runtimes) yang dijelaskan di atas.
Untuk latihan akademis, Anda selalu dapat membungkusnya langsung ke dalam RuntimeExcepton. Seseorang dapat membuat kerangka kerja kecil juga.
sumber
Anda mungkin ingin melihat proyek ini ; Saya membuatnya secara khusus karena masalah pengecualian diperiksa dan antarmuka fungsional.
Dengannya Anda dapat melakukan ini alih-alih menulis kode khusus:
Karena semua antarmuka Throwing * memperluas rekan-rekan non Throwing mereka, Anda kemudian dapat menggunakannya secara langsung dalam aliran:
Atau Anda bisa:
Dan hal lainnya.
sumber
fields.forEach((ThrowingConsumer<Field>) f -> out.println(f.get(o)))