Perché Java non consente di generare un'eccezione controllata da un blocco di inizializzazione statico? Qual è stato il motivo di questa decisione progettuale?
Perché Java non consente di generare un'eccezione controllata da un blocco di inizializzazione statico? Qual è stato il motivo di questa decisione progettuale?
Risposte:
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.
static { if(1 < 10) { throw new NullPointerException(); } }
È 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);
}
}
}
catch (Exception e) {
invece.
System.exit(...)
(o equivalente) è la tua unica opzione,
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'è.
main
può generare eccezioni verificate. Ovviamente quelli non possono essere gestiti.
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".
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 Error
non è 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 ... catch
sopra per catturare il ExceptionInInitializerError
2 .
In alcuni casi puoi prenderlo. Ad esempio, se è stata attivata l'inizializzazione della classe chiamando Class.forName(...)
, è possibile racchiudere la chiamata in a try
e intercettare il ExceptionInInitializerError
o il successivo NoClassDefFoundError
.
Tuttavia, se si tenta di recuperare da un ExceptionInInitializerError
utente, 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:
Se è possibile il recupero (completo!) Dall'eccezione all'interno del blocco, farlo.
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).
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.
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...
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? Runtimeexceptions
si propagano.
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());
}
Sono in grado di compilare lanciando un'eccezione selezionata anche ....
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}