Java neden statik başlatma bloğundan işaretli bir istisna atmaya izin vermiyor?


135

Java neden statik başlatma bloğundan işaretli bir istisna atmaya izin vermiyor? Bu tasarım kararının ardındaki neden neydi?


Statik bir blokta ne tür bir duruma atmak istersiniz?
Kai Huppmann

1
Böyle bir şey yapmak istemiyorum. Statik blok içindeki kontrol edilen istisnaları yakalamanın neden zorunlu olduğunu bilmek istiyorum.
missingfaktor

Kontrol edilen bir istisnanın işlenmesini nasıl beklersiniz? Sizi rahatsız ederse, yakalanan istisnayı yeni RuntimeException özel durumu ("Anlatım iletisi", e) ile yeniden atın;
Thorbjørn Ravn Andersen

18
@ ThorbjørnRavnAndersen Java bu durum için bir İstisna türü sağlar: docs.oracle.com/javase/6/docs/api/java/lang/…
smp7d 26:12

@ smp7d Aşağıdaki kevinarpe cevabına ve StephenC'nin yorumuna bakınız. Gerçekten harika bir özellik ama tuzakları var!
Benj

Yanıtlar:


122

Çünkü kaynağınızdaki bu işaretli istisnaları ele almak mümkün değildir. Başlatma işlemi üzerinde herhangi bir kontrole sahip değilsiniz ve statik {} blokları kaynağınızdan çağrılamaz, böylece bunları try-catch ile çevreleyebilirsiniz.

İşaretli bir istisna tarafından belirtilen herhangi bir hatayı işleyemediğiniz için, denetlenen istisnaların statik bloklarının atılmasına izin verilmemesine karar verildi.

Statik blok, kontrol edilen istisnaları atmamalıdır, ancak yine de işaretlenmemiş / çalışma zamanı istisnalarının atılmasına izin verir. Ancak yukarıdaki nedenlere göre bunları da ele alamazsınız.

Özetlemek gerekirse, bu kısıtlama geliştiricinin uygulamanın kurtaramayacağı hatalara yol açabilecek bir şey oluşturmasını engeller (veya en azından zorlaştırır).


69
Aslında bu cevap yanlış. Statik bir bloğa istisnalar atayabilirsiniz. Yapamayacağınız şey, işaretli bir kural dışı durumun statik bir bloktan yayılmasına izin vermektir .
Stephen C

16
Class.forName (..., true, ...) ile dinamik sınıf yüklemesi yapıyorsanız bu istisnayı işleyebilirsiniz. Bu çok sık karşılaştığınız bir şey değil.
LadyCailin

2
static {throw new NullPointerExcpetion ()} - bu da derlenmeyecek!
Kirill Bazarov

4
@KirillBazarov her zaman bir istisna ile sonuçlanan statik başlatıcısı olan bir sınıf derlenmeyecektir (çünkü neden yapmalı?). Bu atma ifadesini bir if yan tümcesine sarın ve hazırsınız.
Kallja

2
@Ravisha, çünkü bu durumda başlatıcı her durumda normal şekilde tamamlanma şansı yoktur. Try-catch ile println tarafından atılan bir istisna olmayabilir ve bu nedenle başlatıcı istisnasız tamamlanma şansına sahiptir. Bu, bir derleme hatası yapan bir istisnanın koşulsuz sonucudur. Bunun için JLS'ye bakın: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Ancak derleyici yine de sizin durumunuza basit bir koşul ekleyerek kandırabilir :static { if(1 < 10) { throw new NullPointerException(); } }
aldanabilir Kosi2801

67

İşaretli herhangi bir özel durumu yakalayıp denetlenmeyen bir özel durum olarak yeniden karşılaştırarak soruna geçici bir çözüm bulabilirsiniz. Bu kontrolsüz istisna sınıfı bir sarmalayıcı iyi çalışır: java.lang.ExceptionInInitializerError.

Basit kod:

protected static class _YieldCurveConfigHelperSingleton {

    public static YieldCurveConfigHelper _staticInstance;

    static {
        try {
            _staticInstance = new YieldCurveConfigHelper();
        }
        catch (IOException | SAXException | JAXBException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
}

1
@DK: Belki de Java sürümünüz bu tür yakalamak koşulunu desteklemiyor olabilir. Deneyin: catch (Exception e) {yerine.
kevinarpe

4
Evet, bunu yapabilirsiniz, ama bu gerçekten kötü bir fikir. Kontrolsüz istisna koyar sınıf ve buna bağlı herhangi bir diğer sınıflar başarısız sadece sınıfları boşaltma çözülebilir devlet. Bu genellikle imkansızdır ve System.exit(...)(veya eşdeğeri) tek seçeneğinizdir
Stephen C

1
@StephenC, bir "üst" sınıf yüklenemezse, kodunuzun çalışmayacağı için bağımlı sınıflarını yüklemenin fiili olarak gereksiz olduğunu düşünebilir miyiz? Böyle bir bağımlı sınıfı yine de yüklemenin gerekli olacağı duruma ilişkin bir örnek verebilir misiniz? Teşekkürler
Benj

Peki ... kod dinamik olarak yüklemeye çalışırsa; örneğin Class.forName aracılığıyla.
Stephen C

21

Bunun gibi görünmesi gerekir (bu geçerli bir Java kodu değildir )

// Not a valid Java Code
static throws SomeCheckedException {
  throw new SomeCheckedException();
}

ama reklamı nereden yakalarsınız? İşaretli istisnalar yakalamak gerektirir. Sınıfı başlatabilecek bazı örnekleri düşünün (veya zaten başlatıldığı için olmayabilir) ve sadece getireceği karmaşıklığın dikkatini çekmek için, örnekleri başka bir statik initalizer'e koydum:

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?
  }
}

Ve başka bir kötü şey -

interface MyInterface {
  final static ClassA a = new ClassA();
}

ClassA'nın kontrol edilen bir istisna atan statik bir başlatıcısı olduğunu düşünün: Bu durumda MyInterface ('gizli' statik başlatıcısı olan bir arayüz) istisnayı atmak veya işlemek zorunda kalır - bir arayüzde istisna işleme? Olduğu gibi bıraksan iyi olur.


7
mainişaretli istisnalar atabilir. Açıkçası bunlar ele alınamaz.
Mekanik salyangoz

@Mekaniksnail: İlginç nokta. Java benim zihinsel modelinde, main()yığın izleme ile istisna yazdırır System.err, sonra çağırır iş parçacığına bağlı bir "büyülü" (varsayılan) Thread.UncaughtExceptionHandler olduğunu varsayalım System.exit(). Sonunda, bu sorunun cevabı muhtemelen: "çünkü Java tasarımcıları böyle söyledi".
kevinarpe

7

Java neden statik başlatma bloğundan işaretli bir istisna atmaya izin vermiyor?

Teknik olarak bunu yapabilirsiniz. Ancak, denetlenen kural dışı durum blok içinde yakalanmalıdır. İşaretli bir istisnanın bloktan yayılmasına izin verilmez .

Teknik olarak, kontrolsüz bir istisnanın statik bir başlatıcı bloğundan yayılması da mümkündür 1 . Ama bunu kasten yapmak gerçekten kötü bir fikir! Sorun, JVM'nin kendisinin kontrol edilmeyen istisnayı yakalaması ve onu sararak bir ExceptionInInitializerError.

Not: bu bir Error normal bir istisna değildir. Ondan kurtulmaya çalışmamalısınız.

Çoğu durumda, istisna yakalanamaz:

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)

2'yitry ... catch yakalamak için yukarıdakilere yerleştirebileceğiniz hiçbir yer yoktur.ExceptionInInitializerError .

Bazı durumlarda yakalayabilirsiniz. Eğer arayarak sınıf başlatma tetiklenen Örneğin, Class.forName(...), bir aramayı çevreleyebileceğim trycatch ve ya ExceptionInInitializerErrorveya sonrakiNoClassDefFoundError .

Eğer denerseniz Ancak, kurtarmak bir gelen ExceptionInInitializerErrorbir yolda engelle yükümlüdür. Sorun, hata atmadan önce JVM soruna neden olan sınıfı "başarısız" olarak işaretler. Sadece kullanamazsınız. Ayrıca, başarısız sınıfa bağımlı diğer sınıflar da başlatma girişiminde bulunurlarsa başarısız olurlar. İlerlemenin tek yolu tüm başarısız sınıfları boşaltmaktır. Bu , dinamik olarak yüklenmiş kod 3 için uygun olabilir , ancak genel olarak değildir.

1 - Statik bir blok koşulsuz olarak kontrol edilmeyen bir istisna atarsa ​​bir derleme hatasıdır .

2 - Sen belki varsayılan bir yakalanmamış istisna işleyicisi kaydederek kesişmesine bunu yapabilmek, ama bu "ana" parçacığı başlatılamıyor çünkü, kurtarmak için izin vermeyecektir.

3 - Başarısız sınıfları kurtarmak isterseniz, onları yükleyen sınıf yükleyiciden kurtulmanız gerekir.


Bu tasarım kararının ardındaki neden neydi?

Programcıyı yapamayan istisnalar atan kod yazmaya karşı korumaktır ele !

Gördüğümüz gibi, statik bir başlatıcıdaki bir istisna tipik bir uygulamayı tuğlaya dönüştürür. Dil tasarımcılarının yapabileceği en iyi şey, kontrol edilen durumla bir derleme hatası olarak ilgilenmektir. (Maalesef, kontrol edilmeyen istisnalar için de bunu yapmak pratik değildir.)


Tamam, bu yüzden kodunuz statik bir başlatıcıda istisnalar atmaya "ihtiyaç duyuyorsa" ne yapmalısınız? Temel olarak, iki alternatif vardır:

  1. Blok içindeki istisnadan (tam!) Kurtarma mümkün ise, bunu yapın.

  2. Aksi takdirde, kodunuzu, başlatma işlemini statik başlatma bloğunda (veya statik değişkenlerin başlatmalarında) gerçekleşmeyecek şekilde yeniden yapılandırın.


Statik başlatma yapmamak için kodun nasıl yapılandırılacağı konusunda genel öneriler var mı?
MasterJoe


1
1) Hiç yok. 2) Kulağa kötü geliyorlar. Onlara bıraktığım yorumlara bakın. Ama sadece yukarıdaki cevabımda söylediklerimi tekrarlıyorum. Cevabımı okuyup anlarsanız, bu "çözümlerin" çözüm olmadığını bilirsiniz.
Stephen C

4

Java Dil Spesifikasyonlarına bir göz atın : statik başlatıcı başarısız olursa , derlenmiş bir istisna ile aniden tamamlanabiliyorsa, derleme zamanı hatası olduğu belirtilir .


5
Bu soruya cevap vermiyor. neden derleme zamanı hatası olduğunu sordu .
Winston Smith

Hmm, bu yüzden herhangi bir RuntimeError atmak mümkün olmalı, çünkü JLS sadece işaretli istisnalardan bahseder.
Andreas Dolk

Bu doğru, ama asla yığın izi olarak görmeyeceksin. Bu yüzden statik başlatma bloklarına dikkat etmelisiniz.
EJB

2
@EJB: Bu yanlış. Ben sadece denedim ve aşağıdaki kod bana görsel bir stacktrace verdi: public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }Çıktı: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

"Neden" bölümü, muhtemelen daha fazla ilgilendiğiniz yığın izini gösterir.
LadyCailin

2

Yazdığınız hiçbir kod statik başlatma bloğunu çağıramadığından, işaretli atmak yararlı değildir exceptions. Mümkünse, kontrol edilen istisnalar atıldığında jvm ne yapardı? Runtimeexceptionsyayılır.


1
Evet, şimdi anlıyorum. Böyle bir soru göndermek benim için çok aptalcaydı. Ama ne yazık ki ... Şimdi silemiyorum. :( Bununla birlikte, yanıtınız için +1 ...
missingfaktor

1
@fast, Aslında, işaretli istisnalar RuntimeExceptions'a dönüştürülmez. Kendiniz bayt kodu yazarsanız, kontrol edilen istisnaları statik başlatıcı içine kalbinizin içeriğine atabilirsiniz. JVM istisna kontrolünü hiç umursamıyor; tamamen bir Java dili yapısıdır.
Antimon

0

Örneğin: Spring's DispatcherServlet (org.springframework.web.servlet.DispatcherServlet), denetlenen bir özel durumu yakalayan ve başka bir denetlenmeyen özel durum atan senaryoyu işler.

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());
    }

1
Bu yaklaşım, denetlenmeyen istisnanın yakalanamaması sorunudur. Bunun yerine, sınıfı ve ona bağımlı diğer sınıfları kurtarılamaz duruma getirir.
Stephen C

@StephenC - Kurtarılabilir bir duruma sahip olmak istediğimiz basit bir örnek verebilir misiniz?
MasterJoe

Varsayımsal olarak ... uygulamanın devam edebilmesi için IOException özel durumundan kurtulabilmek istiyorsanız. Bunu yapmak istiyorsanız, istisnayı yakalamanız ve aslında ele almanız gerekir ... kontrol edilmeyen bir istisna atmayın.
Stephen C

-5

Ayrıca kontrol edilen bir İstisna atma derlemek mümkün ....

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}

3
Evet ama statik blokta yakalıyorsunuz. İşaretli bir istisnayı statik bir blok içinden dışarıya atmanıza izin verilmez.
ArtOfWarfare
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.