Mengapa Java tidak mengizinkan untuk menampilkan pengecualian yang dicentang dari blok inisialisasi statis? Apa alasan di balik keputusan desain ini?
Mengapa Java tidak mengizinkan untuk menampilkan pengecualian yang dicentang dari blok inisialisasi statis? Apa alasan di balik keputusan desain ini?
Jawaban:
Karena tidak mungkin menangani pengecualian yang dicentang ini di sumber Anda. Anda tidak memiliki kendali apa pun atas proses inisialisasi dan blok {} statis tidak dapat dipanggil dari sumber Anda sehingga Anda bisa mengelilinginya dengan try-catch.
Karena Anda tidak dapat menangani kesalahan apa pun yang ditunjukkan oleh pengecualian yang dicentang, maka diputuskan untuk tidak mengizinkan pelemparan blok statis pengecualian yang diperiksa.
Blok statis tidak boleh menampilkan pengecualian yang dicentang tetapi masih memungkinkan pengecualian yang tidak dicentang / runtime-pengecualian untuk dilemparkan. Tetapi menurut alasan di atas Anda juga tidak dapat menangani ini.
Untuk meringkas, pembatasan ini mencegah (atau setidaknya mempersulit) pengembang untuk membuat sesuatu yang dapat mengakibatkan kesalahan yang tidak dapat dipulihkan oleh aplikasi.
static { if(1 < 10) { throw new NullPointerException(); } }
Anda dapat mengatasi masalah ini dengan menangkap setiap pengecualian yang dicentang dan menampilkannya kembali sebagai pengecualian yang tidak dicentang. Kelas pengecualian dicentang ini bekerja dengan baik sebagai pembungkus: java.lang.ExceptionInInitializerError
.
Kode sampel:
protected static class _YieldCurveConfigHelperSingleton {
public static YieldCurveConfigHelper _staticInstance;
static {
try {
_staticInstance = new YieldCurveConfigHelper();
}
catch (IOException | SAXException | JAXBException e) {
throw new ExceptionInInitializerError(e);
}
}
}
catch (Exception e) {
sebagai gantinya.
System.exit(...)
(atau setara) adalah satu-satunya pilihan Anda,
Ini harus terlihat seperti ini (ini bukan kode Java yang valid)
// Not a valid Java Code
static throws SomeCheckedException {
throw new SomeCheckedException();
}
tapi bagaimana iklan di mana Anda menangkapnya? Pengecualian yang dicentang perlu ditangkap. Bayangkan beberapa contoh yang mungkin menginisialisasi kelas (atau mungkin tidak karena sudah diinisialisasi), dan hanya untuk menarik perhatian dari kerumitan yang akan diperkenalkan, saya meletakkan contoh di initalizer statis lain:
static {
try {
ClassA a = new ClassA();
Class<ClassB> clazz = Class.forName(ClassB.class);
String something = ClassC.SOME_STATIC_FIELD;
} catch (Exception oops) {
// anybody knows which type might occur?
}
}
Dan hal buruk lainnya -
interface MyInterface {
final static ClassA a = new ClassA();
}
Bayangkan ClassA memiliki penginisialisasi statis yang melontarkan pengecualian yang dicentang: Dalam hal ini MyInterface (yang merupakan antarmuka dengan penginisialisasi statis 'tersembunyi') harus membuang pengecualian atau menanganinya - penanganan pengecualian pada antarmuka? Lebih baik biarkan apa adanya.
main
bisa melempar pengecualian yang dicentang. Jelas itu tidak bisa ditangani.
main()
yang mencetak pengecualian dengan pelacakan tumpukan ke System.err
, lalu memanggil System.exit()
. Pada akhirnya, jawaban untuk pertanyaan ini mungkin: "karena desainer Java berkata demikian".
Mengapa Java tidak mengizinkan untuk menampilkan pengecualian yang dicentang dari blok inisialisasi statis?
Secara teknis, Anda bisa melakukan ini. Namun, pengecualian yang dicentang harus ditangkap di dalam blok. Pengecualian yang dicentang tidak diperbolehkan untuk menyebar keluar dari blok.
Secara teknis, dimungkinkan juga untuk mengizinkan pengecualian yang tidak dicentang untuk menyebar keluar dari blok penginisialisasi statis 1 . Tapi itu ide yang sangat buruk untuk melakukan ini dengan sengaja! Masalahnya adalah bahwa JVM sendiri menangkap pengecualian yang tidak dicentang, dan membungkusnya dan menampilkannya kembali sebagai file ExceptionInInitializerError
.
NB: itu Error
bukan pengecualian biasa. Anda tidak boleh mencoba memulihkannya.
Dalam kebanyakan kasus, pengecualian tidak dapat ditangkap:
public class Test {
static {
int i = 1;
if (i == 1) {
throw new RuntimeException("Bang!");
}
}
public static void main(String[] args) {
try {
// stuff
} catch (Throwable ex) {
// This won't be executed.
System.out.println("Caught " + ex);
}
}
}
$ java Test
Exception in thread "main" java.lang.ExceptionInInitializerError
Caused by: java.lang.RuntimeException: Bang!
at Test.<clinit>(Test.java:5)
Tidak ada tempat di mana Anda dapat menempatkan a try ... catch
di atas untuk menangkap ExceptionInInitializerError
2 .
Dalam beberapa kasus, Anda bisa tertular. Misalnya, jika Anda memicu inisialisasi kelas dengan memanggil Class.forName(...)
, Anda bisa mengapit panggilan dalam a try
dan menangkap ExceptionInInitializerError
atau berikutnya NoClassDefFoundError
.
Namun, jika Anda mencoba memulihkan dari suatu ExceptionInInitializerError
Anda kemungkinan besar akan mengalami hambatan. Masalahnya adalah sebelum melakukan kesalahan, JVM menandai kelas yang menyebabkan masalah sebagai "gagal". Anda tidak akan bisa menggunakannya. Selain itu, kelas lain yang bergantung pada kelas yang gagal juga akan masuk ke status gagal jika mereka mencoba untuk menginisialisasi. Satu-satunya cara untuk meneruskan adalah membongkar semua kelas yang gagal. Itu mungkin layak untuk kode 3 yang dimuat secara dinamis , tetapi secara umum tidak.
1 - Ini adalah kesalahan kompilasi jika blok statis tanpa syarat menampilkan pengecualian yang tidak dicentang.
2 - Anda mungkin dapat mencegatnya dengan mendaftarkan pengendali pengecualian tidak tertangkap default, tetapi itu tidak memungkinkan Anda untuk memulihkan, karena utas "utama" Anda tidak dapat dimulai.
3 - Jika Anda ingin memulihkan kelas yang gagal, Anda harus membuang classloader yang memuatnya.
Apa alasan di balik keputusan desain ini?
Ini untuk melindungi programmer dari menulis kode yang melempar pengecualian yang tidak dapat ditangani!
Seperti yang telah kita lihat, pengecualian dalam penginisialisasi statis mengubah aplikasi biasa menjadi batu bata. Hal terbaik yang dapat dilakukan oleh desainer bahasa adalah menangani kasus yang dicentang sebagai kesalahan kompilasi. (Sayangnya, tidak praktis melakukan ini untuk pengecualian yang tidak dicentang juga.)
Oke, jadi apa yang harus Anda lakukan jika kode "perlu" menampilkan pengecualian dalam penginisialisasi statis. Pada dasarnya ada dua alternatif:
Jika pemulihan (penuh!) Dari pengecualian dalam blok dimungkinkan, maka lakukan itu.
Jika tidak, atur ulang kode Anda sehingga inisialisasi tidak terjadi dalam blok inisialisasi statis (atau dalam penginisialisasi variabel statis).
Lihatlah Spesifikasi Bahasa Java : dinyatakan bahwa ini adalah kesalahan waktu kompilasi jika penginisialisasi statis gagal dapat diselesaikan secara tiba - tiba dengan pengecualian yang dicentang.
public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }
Output:Exception in thread "main" java.lang.ExceptionInInitializerError Caused by: java.lang.RuntimeException: java.lang.ClassNotFoundException: whathappenswhenastaticblockthrowsanexception at Main.<clinit>(Main.java:6) Caused by: java.lang.ClassNotFoundException: whathappen...
Karena tidak ada kode yang Anda tulis yang dapat memanggil blok inisialisasi statis, tidak berguna untuk membuang centang exceptions
. Jika memungkinkan, apa yang akan dilakukan jvm ketika pengecualian yang dicentang dilemparkan? Runtimeexceptions
disebarkan.
Misalnya: Spring's DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) menangani skenario yang menangkap pengecualian yang dicentang dan menampilkan pengecualian lain yang tidak dicentang.
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage());
}
Saya dapat mengkompilasi melempar Exception juga ....
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}