¿Por qué Java no permite lanzar una excepción marcada desde el bloque de inicialización estática?

135

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

missingfaktor
fuente
¿Qué tipo de excepción le gustaría lanzar en qué tipo de situación en un bloque estático?
Kai Huppmann
1
No quiero hacer nada de eso. Solo quiero saber por qué es obligatorio detectar las excepciones marcadas dentro del bloque estático.
missingfaktor
¿Cómo esperaría que se manejara una excepción marcada entonces? Si le molesta, simplemente vuelva a lanzar la excepción capturada con la nueva excepción RuntimeException ("Mensaje revelador", e);
Thorbjørn Ravn Andersen
18
@ ThorbjørnRavnAndersen Java en realidad proporciona un tipo de excepción para esa situación: docs.oracle.com/javase/6/docs/api/java/lang/…
smp7d
@ smp7d Vea la respuesta de kevinarpe a continuación, y su comentario de StephenC. Es una característica realmente genial, ¡pero tiene trampas!
Benj

Respuestas:

122

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.

Kosi2801
fuente
69
En realidad, esta respuesta es inexacta. PUEDE lanzar excepciones en un bloque estático. Lo que no puede hacer es permitir que una excepción marcada se propague fuera de un bloque estático.
Stephen C
16
PUEDE manejar esta excepción, si está cargando clases dinámicas usted mismo, con Class.forName (..., verdadero, ...); De acuerdo, esto no es algo con lo que te encuentres muy a menudo.
LadyCailin
2
static {throw new NullPointerExcpetion ()} - ¡esto tampoco se compilará!
Kirill Bazarov
44
@KirillBazarov una clase con un inicializador estático que siempre da como resultado una excepción no se compilará (porque ¿por qué debería hacerlo?). Envuelva esa declaración de lanzamiento en una cláusula if y estará listo para comenzar.
Kallja
2
@Ravisha porque en ese caso no hay posibilidad de que el inicializador se complete normalmente en ningún caso. Con el try-catch no puede haber ninguna excepción lanzada por println y, por lo tanto, el inicializador tiene la oportunidad de completarse sin excepción. Es el resultado incondicional de una excepción lo que lo convierte en un error de compilación. Vea el JLS para eso: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Pero el compilador aún puede ser engañado agregando una condición simple en su caso:static { if(1 < 10) { throw new NullPointerException(); } }
Kosi2801
67

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);
        }
    }
}
kevinarpe
fuente
1
@DK: Tal vez su versión de Java no sea compatible con este tipo de cláusula catch. Prueba: en su catch (Exception e) {lugar.
kevinarpe
44
Sí, puedes hacer esto, pero es una muy mala idea. La excepción no verificada coloca a la clase y a cualquier otra clase que dependa de ella en un estado fallido que solo puede resolverse descargando las clases. Esto es normalmente imposible, y System.exit(...)(o equivalente) es su única opción,
Stephen C
1
@StephenC, ¿podemos pensar que si una clase "principal" no se carga, de hecho no es necesario cargar sus clases dependientes ya que su código no funcionará? ¿Podría dar algún ejemplo de un caso en el que sería necesario cargar una clase tan dependiente? Gracias
Benj
¿Qué tal ... si el código intenta cargarlo dinámicamente; por ejemplo, a través de Class.forName.
Stephen C
21

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á.

Andreas Dolk
fuente
77
mainpuede lanzar excepciones marcadas. Obviamente, esos no pueden ser manejados.
Caracol mecánico
@Mechanicalsnail: punto interesante. En mi modelo mental de Java, supongo que hay un Thread.UncaughtExceptionHandler "mágico" (predeterminado) adjunto al hilo en ejecución main()que imprime la excepción con el seguimiento de la pila y System.errluego llama System.exit(). Al final, la respuesta a esta pregunta es probablemente: "porque los diseñadores de Java lo dijeron".
kevinarpe
7

¿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 Errorexcepció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 ... catchen el anterior para atrapar el ExceptionInInitializerError2 .

En algunos casos puedes atraparlo. Por ejemplo, si activó la inicialización de la clase llamando Class.forName(...), puede encerrar la llamada en un tryy capturar el ExceptionInInitializerErroro 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:

  1. Si es posible (¡completo!) La recuperación de la excepción dentro del bloque, entonces hazlo.

  2. 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).

Stephen C
fuente
¿Hay alguna recomendación general sobre cómo estructurar el código para que no realice ninguna inicialización estática?
MasterJoe
¿Cómo suenan estas soluciones? stackoverflow.com/a/21321935/6648326 y stackoverflow.com/a/56575807/6648326
MasterJoe el
1
1) No tengo ninguno. 2) Suenan mal. Ver los comentarios que dejé en ellos. Pero solo estoy repitiendo lo que dije en mi respuesta anterior. Si lee y comprende mi respuesta, sabrá que esas "soluciones" no son soluciones.
Stephen C
4

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.

Laurent Etiemble
fuente
55
Sin embargo, esto no responde la pregunta. preguntó por qué es un error de tiempo de compilación.
Winston Smith
Hmm, entonces debería ser posible lanzar cualquier RuntimeError, porque JLS solo menciona excepciones marcadas.
Andreas Dolk el
Así es, pero nunca lo verás como una traza de pila. Es por eso que debe tener cuidado con los bloques de inicialización estática.
EJB
2
@EJB: Esto es incorrecto. Acabo de probarlo y el siguiente código me dio un seguimiento visual de la pila: 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...
Konrad Höffner
La parte "Causado por" muestra la traza de la pila en la que probablemente estés más interesado.
LadyCailin
2

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? Runtimeexceptionsse propagan hacia arriba.

fastcodejava
fuente
1
Bueno, sí, ahora entiendo la cosa. Fue muy tonto de mi parte publicar una pregunta como esta. Pero, por desgracia ... no puedo eliminarlo ahora. :( Sin embargo, +1 por tu respuesta ...
missingfaktor
1
@fast, en realidad, las excepciones marcadas NO se convierten en RuntimeExceptions. Si escribe el código de bytes usted mismo, puede lanzar las excepciones marcadas dentro de un inicializador estático al contenido de su corazón. La JVM no se preocupa por la comprobación de excepciones en absoluto; Es puramente una construcción de lenguaje Java.
Antimonio
0

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());
    }
pcdhan
fuente
1
Esto aborda el problema de que no se puede detectar la excepción no verificada. En cambio, coloca la clase y cualquier otra clase que dependa de ella en un estado irrecuperable.
Stephen C
@StephenC: ¿podría dar un ejemplo simple en el que quisiéramos tener un estado recuperable?
MasterJoe
Hipotéticamente ... si quisieras poder recuperarte de la IOException para que la aplicación pudiera continuar. Si quieres hacer eso, entonces debes atrapar la excepción y manejarla ... no lanzar una excepción no marcada.
Stephen C
-5

Puedo compilar lanzando una excepción marcada también ...

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}
usuario2775569
fuente
3
Sí, pero lo estás atrapando dentro del bloque estático. No está permitido lanzar una excepción marcada desde el interior de un bloque estático hacia afuera.
ArtOfWarfare