Perché Java non consente di generare un'eccezione controllata dal blocco di inizializzazione statica?


135

Perché Java non consente di generare un'eccezione controllata da un blocco di inizializzazione statico? Qual è stato il motivo di questa decisione progettuale?


Che tipo di eccezione desideri inserire in quale tipo di situazione in un blocco statico?
Kai Huppmann,

1
Non voglio fare niente del genere. Voglio solo sapere perché è obbligatorio rilevare le eccezioni verificate all'interno del blocco statico.
missingfaktor il

Come ti aspetti che venga gestita un'eccezione controllata? Se ti dà fastidio, ripeti l'eccezione rilevata con il lancio di RuntimeException ("Messaggio di narrazione", e);
Thorbjørn Ravn Andersen,

18
@ ThorbjørnRavnAndersen Java in realtà fornisce un tipo di eccezione per quella situazione: docs.oracle.com/javase/6/docs/api/java/lang/…
smp7d

@ smp7d Vedi la risposta di kevinarpe sotto e il suo commento di StephenC. È una funzionalità davvero interessante ma ha trappole!
Benj,

Risposte:


122

Perché non è possibile gestire queste eccezioni verificate nella propria fonte. Non hai alcun controllo sul processo di inizializzazione e i blocchi {} statici non possono essere richiamati dal tuo sorgente in modo da poterli circondare con try-catch.

Poiché non è possibile gestire alcun errore indicato da un'eccezione verificata, è stato deciso di impedire il lancio di blocchi statici di eccezioni verificate.

Il blocco statico non deve generare eccezioni verificate ma consente comunque di generare eccezioni non controllate / di runtime. Ma secondo le ragioni di cui sopra non saresti in grado di gestirle neanche tu.

Riassumendo, questa restrizione impedisce (o almeno rende più difficile) per lo sviluppatore di creare qualcosa che può provocare errori da cui l'applicazione non sarebbe in grado di recuperare.


69
In realtà, questa risposta è imprecisa. È possibile generare eccezioni in un blocco statico. Quello che non puoi fare è consentire a un'eccezione controllata di propagarsi da un blocco statico.
Stephen C,

16
PUOI gestire questa eccezione, se stai eseguendo il caricamento dinamico della classe, con Class.forName (..., true, ...); Certo, questo non è qualcosa che incontri molto spesso.
LadyCailin,

2
static {throw new NullPointerExcpetion ()} - anche questo non verrà compilato!
Kirill Bazarov,

4
@KirillBazarov una classe con un inizializzatore statico che si traduce sempre in un'eccezione non verrà compilata (perché perché dovrebbe?). Avvolgi quella frase di lancio in una clausola if e sei a posto.
Kallja,

2
@Ravisha perché in tal caso non è possibile che l'inizializzatore si completi normalmente. Con il try-catch potrebbe non esserci alcuna eccezione generata da println e quindi l'inizializzatore ha la possibilità di completarsi senza eccezioni. È il risultato incondizionato di un'eccezione che lo rende un errore di compilazione. Vedi il JLS per questo: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Ma il compilatore può ancora essere ingannato aggiungendo una semplice condizione nel tuo caso:static { if(1 < 10) { throw new NullPointerException(); } }
Kosi2801

67

È possibile aggirare il problema rilevando qualsiasi eccezione verificata e rilanciandola come un'eccezione non selezionata. Questa classe di eccezioni incontrollato funziona bene come un wrapper: java.lang.ExceptionInInitializerError.

Codice di esempio:

protected static class _YieldCurveConfigHelperSingleton {

    public static YieldCurveConfigHelper _staticInstance;

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

1
@DK: forse la tua versione di Java non supporta questo tipo di clausola catch. Prova: catch (Exception e) {invece.
kevinarpe,

4
Sì, puoi farlo, ma è una pessima idea. L'eccezione non selezionata mette la classe e tutte le altre classi che dipendono dal suo stato fallito che può essere risolto solo scaricando le classi. Questo è generalmente impossibile, e System.exit(...)(o equivalente) è la tua unica opzione,
Stephen C

1
@StephenC possiamo pensare che se una classe "parent" non riesce a caricarsi, è di fatto inutile caricare le sue classi dipendenti poiché il tuo codice non funzionerà? Potresti fornire qualche esempio di un caso in cui sarebbe comunque necessario caricare una tale classe dipendente? Grazie
Benj

Che ne dici di ... se il codice tenta di caricarlo in modo dinamico; ad es. tramite Class.forName.
Stephen C

21

Dovrebbe apparire così (questo non è un codice Java valido)

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

ma come sarebbe annuncio dove lo prendi? Le eccezioni verificate richiedono la cattura. Immagina alcuni esempi che possono inizializzare la classe (o no perché è già inizializzata), e solo per attirare l'attenzione sulla complessità di ciò che introdurrebbe, inserisco gli esempi in un altro inizializzatore statico:

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

E un'altra brutta cosa -

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

Immagina che ClassA avesse un inizializzatore statico che genera un'eccezione controllata: in questo caso MyInterface (che è un'interfaccia con un inizializzatore statico "nascosto") dovrebbe lanciare l'eccezione o gestirla - gestione delle eccezioni su un'interfaccia? Meglio lasciarlo così com'è.


7
mainpuò generare eccezioni verificate. Ovviamente quelli non possono essere gestiti.
Lumaca meccanica

@Mechanicalsnail: punto interessante. Nel mio modello mentale di Java, suppongo che esista un Thread.UncaughtExceptionHandler "magico" (predefinito) collegato al thread in esecuzione in main()cui viene stampata l'eccezione con stack stack System.err, quindi chiama System.exit(). Alla fine, la risposta a questa domanda è probabilmente: "perché lo hanno detto i progettisti Java".
Kevinevpe,

7

Perché Java non consente di generare un'eccezione controllata da un blocco di inizializzazione statico?

Tecnicamente, puoi farlo. Tuttavia, l'eccezione selezionata deve essere rilevata all'interno del blocco. Un'eccezione verificata non può propagarsi fuori dal blocco.

Tecnicamente, è anche possibile consentire a un'eccezione non selezionata di propagarsi da un blocco di inizializzatore statico 1 . Ma è una pessima idea farlo deliberatamente! Il problema è che la JVM stessa rileva l'eccezione non selezionata, la avvolge e la ricodifica come a ExceptionInInitializerError.

NB: questa Errornon è un'eccezione regolare. Non dovresti tentare di recuperare da esso.

Nella maggior parte dei casi, l'eccezione non può essere rilevata:

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)

Non c'è nessun posto in cui puoi posizionare un try ... catchsopra per catturare il ExceptionInInitializerError2 .

In alcuni casi puoi prenderlo. Ad esempio, se è stata attivata l'inizializzazione della classe chiamando Class.forName(...), è possibile racchiudere la chiamata in a trye intercettare il ExceptionInInitializerErroro il successivo NoClassDefFoundError.

Tuttavia, se si tenta di recuperare da un ExceptionInInitializerErrorutente, si rischia di imbattersi in un blocco stradale. Il problema è che prima di lanciare l'errore, la JVM contrassegna la classe che ha causato il problema come "non riuscita". Semplicemente non sarai in grado di usarlo. Inoltre, tutte le altre classi che dipendono dalla classe non riuscita passeranno allo stato non riuscito se tentano di inizializzare. L'unica strada da percorrere è scaricare tutte le classi non riuscite. Che potrebbe essere fattibile per codice caricato dinamicamente 3 , ma in generale non è.

1 - È un errore di compilazione se un blocco statico genera incondizionatamente un'eccezione non selezionata.

2 - Si potrebbe essere in grado di intercettarlo registrando un gestore di eccezioni non rilevate default, ma che non permetterà di recuperare, perché il filo "principale" non può iniziare.

3 - Se si desidera ripristinare le classi non riuscite, è necessario eliminare il programma di caricamento classi che le ha caricate.


Qual è stato il motivo di questa decisione progettuale?

È per proteggere il programmatore dalla scrittura di codice che genera eccezioni che non possono essere gestite!

Come abbiamo visto, un'eccezione in un inizializzatore statico trasforma un'applicazione tipica in un mattone. La cosa migliore che i progettisti del linguaggio potrebbero fare è trattare il caso verificato come un errore di compilazione. (Sfortunatamente, non è pratico farlo anche per eccezioni non controllate.)


OK, quindi cosa dovresti fare se il tuo codice "ha bisogno" di generare eccezioni in un inizializzatore statico. Fondamentalmente, ci sono due alternative:

  1. Se è possibile il recupero (completo!) Dall'eccezione all'interno del blocco, farlo.

  2. In caso contrario, ristrutturare il codice in modo che l'inizializzazione non avvenga in un blocco di inizializzazione statica (o negli inizializzatori di variabili statiche).


Esistono raccomandazioni generali su come strutturare il codice in modo che non esegua l'inizializzazione statica?
MasterJoe


1
1) Non ne ho. 2) Suonano male. Vedi i commenti che ho lasciato su di loro. Ma sto solo ripetendo quello che ho detto nella mia risposta sopra. Se leggi e comprendi la mia risposta, saprai che quelle "soluzioni" non sono soluzioni.
Stephen C,

4

Dai un'occhiata alle specifiche del linguaggio Java : si afferma che è un errore di compilazione se l'inizializzatore statico fallisce è in grado di completare bruscamente con un'eccezione controllata.


5
Questo non risponde alla domanda però. ha chiesto perché è un errore di compilazione.
Winston Smith,

Hmm, quindi lanciare qualsiasi RuntimeError dovrebbe essere possibile, perché JLS menziona solo le eccezioni verificate.
Andreas Dolk,

Esatto, ma non vedrai mai come stacktrace. Ecco perché devi stare attento con i blocchi di inizializzazione statici.
EJB

2
@EJB: questo non è corretto. Ho appena provato e il seguente codice mi ha dato una traccia visiva dello stack: 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

La parte "Causato da" mostra la traccia dello stack che probabilmente ti interessa di più.
LadyCailin

2

Dato che nessun codice che scrivi può chiamare un blocco di inizializzazione statica, non è utile lanciare un segno di spunta exceptions. Se fosse possibile, cosa farebbe la jvm quando vengono generate eccezioni controllate? Runtimeexceptionssi propagano.


1
Bene, sì, ora capisco la cosa. È stato molto sciocco da parte mia pubblicare una domanda come questa. Ma ahimè ... non posso cancellarlo ora. :( Tuttavia, +1 per la tua risposta ...
missingfaktor il

1
@fast, In realtà, le eccezioni selezionate NON vengono convertite in RuntimeExceptions. Se scrivi tu stesso il codice, puoi aggiungere eccezioni verificate all'interno di un inizializzatore statico al contenuto del tuo cuore. Alla JVM non interessa affatto il controllo delle eccezioni; è puramente un costrutto in linguaggio Java.
Antimonio

0

Ad esempio: Spring's DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) gestisce lo scenario che rileva un'eccezione verificata e genera un'altra eccezione non selezionata.

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
Questo approccio al problema che l'eccezione non selezionata non può essere colta. Invece mette la classe e tutte le altre classi che dipendono da essa in uno stato irrecuperabile.
Stephen C,

@StephenC - Potresti per favore fare un semplice esempio in cui vorremmo avere uno stato recuperabile?
MasterJoe

Ipoteticamente ... se si desidera essere in grado di recuperare da IOException in modo che l'applicazione possa continuare. Se vuoi farlo, allora devi prendere l'eccezione e gestirla effettivamente ... non lanciare un'eccezione non selezionata.
Stephen C

-5

Sono in grado di compilare lanciando un'eccezione selezionata anche ....

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

3
Sì, ma lo stai rilevando all'interno del blocco statico. Non è consentito lanciare un'eccezione controllata dall'interno di un blocco statico all'esterno.
ArtOfWarfare il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.