Mengapa Java tidak mengizinkan untuk menampilkan pengecualian yang dicentang dari blok inisialisasi statis?

136

Mengapa Java tidak mengizinkan untuk menampilkan pengecualian yang dicentang dari blok inisialisasi statis? Apa alasan di balik keputusan desain ini?

missingfaktor
sumber
Pengecualian macam apa yang ingin Anda berikan dalam situasi seperti apa di blok statis?
Kai Huppmann
1
Saya tidak ingin melakukan hal seperti itu. Saya hanya ingin tahu mengapa wajib untuk menangkap pengecualian yang dicentang di dalam blok statis.
missingfaktor
Bagaimana Anda mengharapkan pengecualian yang dicentang akan ditangani? Jika itu mengganggu Anda, cukup luncurkan kembali pengecualian yang tertangkap dengan melempar RuntimeException baru ("Telling message", e);
Thorbjørn Ravn Andersen
18
@ ThorbjørnRavnAndersen Java sebenarnya menyediakan tipe Pengecualian untuk situasi tersebut: docs.oracle.com/javase/6/docs/api/java/lang/…
smp7d
@ smp7d Lihat jawaban kevinarpe di bawah ini, dan komentarnya dari StephenC. Ini adalah fitur yang sangat keren tetapi memiliki jebakan!
Benj

Jawaban:

124

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.

Kosi2801
sumber
70
Sebenarnya jawaban ini tidak akurat. Anda BISA melempar pengecualian dalam blok statis. Apa yang tidak dapat Anda lakukan adalah mengizinkan pengecualian yang dicentang untuk menyebar keluar dari blok statis.
Stephen C
16
Anda BISA menangani pengecualian ini, jika Anda melakukan pemuatan kelas dinamis sendiri, dengan Class.forName (..., true, ...); Memang, ini bukanlah sesuatu yang sering Anda temui.
LadyCailin
2
statis {lempar NullPointerExcpetion ()} baru - ini juga tidak akan dikompilasi!
Kirill Bazarov
4
@KirillBazarov kelas dengan penginisialisasi statis yang selalu menghasilkan pengecualian tidak akan dikompilasi (karena mengapa harus demikian?). Bungkus pernyataan lemparan itu dengan klausa if dan Anda siap melakukannya.
Kallja
2
@Ravisha karena dalam hal ini tidak ada kesempatan bagi penginisialisasi untuk menyelesaikan secara normal dalam hal apapun. Dengan try-catch mungkin tidak ada pengecualian yang dikeluarkan oleh println dan oleh karena itu penginisialisasi memiliki kesempatan untuk menyelesaikan tanpa pengecualian. Ini adalah hasil pengecualian tanpa syarat yang membuatnya menjadi kesalahan kompilasi. Lihat JLS untuk itu: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Tetapi penyusun mungkin masih tertipu dengan menambahkan kondisi sederhana dalam kasus Anda:static { if(1 < 10) { throw new NullPointerException(); } }
Kosi2801
67

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);
        }
    }
}
kevinarpe
sumber
1
@DK: Mungkin versi Java Anda tidak mendukung klausa catch jenis ini. Coba: catch (Exception e) {sebagai gantinya.
kevinarpe
4
Ya, Anda bisa melakukan ini, tetapi itu ide yang sangat buruk. Pengecualian yang tidak dicentang menempatkan kelas dan kelas lain yang bergantung padanya status gagal yang hanya dapat diselesaikan dengan membongkar kelas. Itu biasanya tidak mungkin, dan System.exit(...)(atau setara) adalah satu-satunya pilihan Anda,
Stephen C
1
@ StephenC dapatkah kita berpikir bahwa jika kelas "induk" gagal dimuat, secara de facto tidak perlu memuat kelas dependennya karena kode Anda tidak akan berfungsi? Bisakah Anda memberikan beberapa contoh kasus di mana akan perlu untuk memuat kelas yang bergantung seperti itu? Terima kasih
Benj
Bagaimana dengan ... jika kode mencoba memuatnya secara dinamis; misalnya melalui Class.forName.
Stephen C
21

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.

Andreas Dolk
sumber
7
mainbisa melempar pengecualian yang dicentang. Jelas itu tidak bisa ditangani.
Siput mekanik
@Mechanicalsnail: Hal yang menarik. Dalam model mental saya Java, saya berasumsi bahwa ada Thread.UncaughtExceptionHandler "magis" (default) yang terpasang ke thread yang berjalan 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".
kevinarpe
7

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 Errorbukan 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 ... catchdi atas untuk menangkap ExceptionInInitializerError2 .

Dalam beberapa kasus, Anda bisa tertular. Misalnya, jika Anda memicu inisialisasi kelas dengan memanggil Class.forName(...), Anda bisa mengapit panggilan dalam a trydan menangkap ExceptionInInitializerErroratau berikutnya NoClassDefFoundError.

Namun, jika Anda mencoba memulihkan dari suatu ExceptionInInitializerErrorAnda 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:

  1. Jika pemulihan (penuh!) Dari pengecualian dalam blok dimungkinkan, maka lakukan itu.

  2. Jika tidak, atur ulang kode Anda sehingga inisialisasi tidak terjadi dalam blok inisialisasi statis (atau dalam penginisialisasi variabel statis).

Stephen C
sumber
Apakah ada rekomendasi umum tentang cara menyusun kode sehingga tidak melakukan inisialisasi statis?
MasterJoe
Bagaimana solusi ini terdengar? stackoverflow.com/a/21321935/6648326 dan stackoverflow.com/a/56575807/6648326
MasterJoe
1
1) Saya tidak punya. 2) Kedengarannya buruk. Lihat komentar yang saya tinggalkan untuk mereka. Tapi saya hanya mengulangi apa yang saya katakan dalam Jawaban saya di atas. Jika Anda membaca dan memahami Jawaban saya, Anda akan tahu bahwa "solusi" itu bukanlah solusi.
Stephen C
4

Lihatlah Spesifikasi Bahasa Java : dinyatakan bahwa ini adalah kesalahan waktu kompilasi jika penginisialisasi statis gagal dapat diselesaikan secara tiba - tiba dengan pengecualian yang dicentang.

Laurent Etiemble
sumber
5
Ini tidak menjawab pertanyaan itu. dia bertanya mengapa itu adalah kesalahan waktu kompilasi.
Winston Smith
Hmm, jadi melempar RuntimeError apa pun bisa dilakukan, karena JLS hanya menyebutkan pengecualian yang dicentang.
Andreas Dolk
Benar, tetapi Anda tidak akan pernah melihatnya sebagai pelacakan tumpukan. Itulah mengapa Anda perlu berhati-hati dengan blok inisialisasi statis.
EJB
2
@EJB: Ini salah. Saya baru saja mencobanya dan kode berikut memberi saya stacktrace visual: 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...
Konrad Höffner
Bagian "Disebabkan oleh" menunjukkan jejak tumpukan yang mungkin lebih Anda minati.
LadyCailin
2

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? Runtimeexceptionsdisebarkan.

fastcodejava.dll
sumber
1
Nah, ya saya mengerti sekarang. Sangat konyol bagi saya untuk memposting pertanyaan seperti ini. Tapi sayang ... Saya tidak bisa menghapusnya sekarang. :( Namun demikian, +1 untuk tanggapan Anda ...
missingfaktor
1
@fast, Sebenarnya, pengecualian yang dicentang TIDAK diubah menjadi RuntimeExceptions. Jika Anda menulis bytecode sendiri, Anda dapat melempar pengecualian yang dicentang di dalam penginisialisasi statis ke isi hati Anda. JVM sama sekali tidak peduli dengan pemeriksaan pengecualian; itu murni konstruksi bahasa Java.
Antimony
0

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());
    }
pcdhan
sumber
1
Ini mendekati masalah bahwa pengecualian yang tidak dicentang tidak dapat ditangkap. Sebaliknya, ia menempatkan kelas dan kelas lain yang bergantung padanya ke dalam keadaan tidak dapat dipulihkan.
Stephen C
@StephenC - Bisakah Anda memberikan contoh sederhana di mana kami ingin memiliki status yang dapat dipulihkan?
MasterJoe
Secara hipotetis ... jika Anda ingin dapat memulihkan dari IOException sehingga aplikasi dapat dilanjutkan. Jika Anda ingin melakukan itu, maka Anda harus menangkap pengecualian dan benar-benar menanganinya ... tidak membuang pengecualian yang tidak dicentang.
Stephen C
-5

Saya dapat mengkompilasi melempar Exception juga ....

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}
pengguna2775569
sumber
3
Ya tapi Anda menangkapnya di dalam blok statis. Anda tidak diperbolehkan melempar pengecualian yang dicentang dari dalam blok statis ke luarnya.
ArtOfWarfare