¿Por qué Java no permite lanzar una excepción marcada desde un bloque de inicialización estática? ¿Cuál fue la razón detrás de esta decisión de diseño?
¿Por qué Java no permite lanzar una excepción marcada desde un bloque de inicialización estática? ¿Cuál fue la razón detrás de esta decisión de diseño?
Respuestas:
Porque no es posible manejar estas excepciones marcadas en su fuente. No tiene ningún control sobre el proceso de inicialización y los bloques estáticos {} no se pueden invocar desde su fuente para que pueda rodearlos con try-catch.
Como no puede manejar ningún error indicado por una excepción marcada, se decidió no permitir el lanzamiento de bloques estáticos de excepciones marcadas.
El bloque estático no debe arrojar excepciones comprobadas, pero aún permite que se arrojen excepciones no verificadas / en tiempo de ejecución. Pero de acuerdo con las razones anteriores, tampoco podrá manejarlos.
Para resumir, esta restricción impide (o al menos dificulta) que el desarrollador construya algo que pueda generar errores de los que la aplicación no podrá recuperarse.
static { if(1 < 10) { throw new NullPointerException(); } }
Puede solucionar el problema detectando cualquier excepción marcada y volviéndola a lanzar como una excepción no verificada. Esta clase de excepción sin control funciona bien como un envoltorio: java.lang.ExceptionInInitializerError
.
Código de muestra:
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) {
lugar.
System.exit(...)
(o equivalente) es su única opción,
Tendría que tener este aspecto (este no es un código Java válido)
// Not a valid Java Code
static throws SomeCheckedException {
throw new SomeCheckedException();
}
pero ¿cómo sería el anuncio donde lo atrapas? Las excepciones marcadas requieren captura. Imagine algunos ejemplos que pueden inicializar la clase (o no porque ya está inicializada), y solo para llamar la atención sobre la complejidad que introduciría, puse los ejemplos en otro inicializador estático:
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?
}
}
Y otra cosa desagradable ...
interface MyInterface {
final static ClassA a = new ClassA();
}
Imagine ClassA tenía un inicializador estático que arrojaba una excepción marcada: en este caso, MyInterface (que es una interfaz con un inicializador estático 'oculto') tendría que lanzar la excepción o manejarla: ¿manejo de excepción en una interfaz? Mejor déjalo como está.
main
puede lanzar excepciones marcadas. Obviamente, esos no pueden ser manejados.
main()
que imprime la excepción con el seguimiento de la pila y System.err
luego llama System.exit()
. Al final, la respuesta a esta pregunta es probablemente: "porque los diseñadores de Java lo dijeron".
¿Por qué Java no permite lanzar una excepción marcada desde un bloque de inicialización estática?
Técnicamente, puedes hacer esto. Sin embargo, la excepción marcada debe quedar atrapada dentro del bloque. Una excepción marcada no puede propagarse fuera del bloque.
Técnicamente, también es posible permitir que una excepción no verificada se propague desde un bloque inicializador estático 1 . ¡Pero es una muy mala idea hacer esto deliberadamente! El problema es que la propia JVM detecta la excepción no verificada, la envuelve y la vuelve a lanzar como a ExceptionInInitializerError
.
NB: esa no es una Error
excepción regular. No debe intentar recuperarse de él.
En la mayoría de los casos, no se puede detectar la excepción:
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)
No hay ningún lugar donde pueda colocar un try ... catch
en el anterior para atrapar el ExceptionInInitializerError
2 .
En algunos casos puedes atraparlo. Por ejemplo, si activó la inicialización de la clase llamando Class.forName(...)
, puede encerrar la llamada en un try
y capturar el ExceptionInInitializerError
o el siguiente NoClassDefFoundError
.
Sin embargo, si intenta recuperarse de una ExceptionInInitializerError
, es probable que se encuentre con un obstáculo. El problema es que antes de arrojar el error, la JVM marca la clase que causó el problema como "fallida". Simplemente no podrás usarlo. Además, cualquier otra clase que dependa de la clase fallida también entrará en estado fallido si intentan inicializarse. El único camino a seguir es descargar todas las clases fallidas. Eso podría ser factible para el código 3 cargado dinámicamente , pero en general no lo es.
1 - Es un error de compilación si un bloque estático genera incondicionalmente una excepción no verificada.
2 - Es posible que pueda interceptarlo registrando un controlador de excepción no capturado predeterminado, pero eso no le permitirá recuperarse, porque su hilo "principal" no puede iniciarse.
3 - Si quisieras recuperar las clases fallidas, deberías deshacerte del cargador de clases que las cargó.
¿Cuál fue la razón detrás de esta decisión de diseño?
¡Es para proteger al programador de escribir código que arroja excepciones que no se pueden manejar!
Como hemos visto, una excepción en un inicializador estático convierte una aplicación típica en un ladrillo. Lo mejor que los diseñadores de lenguaje podrían hacer es tratar el caso marcado como un error de compilación. (Desafortunadamente, tampoco es práctico hacer esto para excepciones no verificadas).
Bien, entonces, ¿qué debe hacer si su código "necesita" lanzar excepciones en un inicializador estático? Básicamente, hay dos alternativas:
Si es posible (¡completo!) La recuperación de la excepción dentro del bloque, entonces hazlo.
De lo contrario, reestructura tu código para que la inicialización no ocurra en un bloque de inicialización estática (o en los inicializadores de variables estáticas).
Echar un vistazo a las especificaciones Lenguaje Java : se afirma que se trata de un error de tiempo de compilación si inicializador estático no es capaz de completar bruscamente con una excepción comprobada.
public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }
Salida: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...
Como ningún código que escriba puede llamar al bloque de inicialización estático, no es útil lanzarlo marcado exceptions
. Si fuera posible, ¿qué haría el jvm cuando se lanzan excepciones marcadas? Runtimeexceptions
se propagan hacia arriba.
Por ejemplo: Spring's DispatcherServlet (org.springframework.web.servlet.DispatcherServlet) maneja el escenario que captura una excepción marcada y arroja otra excepción no marcada.
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());
}
Puedo compilar lanzando una excepción marcada también ...
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}