Por que o Java não permite lançar uma exceção verificada do bloco de inicialização estática?


135

Por que o Java não permite lançar uma exceção verificada de um bloco de inicialização estático? Qual foi o motivo por trás dessa decisão de design?


Que tipo de exceção você gostaria de lançar em que tipo de situação em um bloco estático?
— Kai Huppmann

1
Eu não quero fazer nada assim. Eu só quero saber por que é obrigatório capturar as exceções verificadas dentro do bloco estático.
— precisa saber é o seguinte

Como você esperaria que uma exceção marcada fosse tratada então? Se isso lhe incomoda, basta repetir a exceção capturada com throw new RuntimeException ("Telling message", e);
— Thorbjørn Ravn Andersen

18
@ ThorbjørnRavnAndersen Java realmente fornece um tipo de exceção para essa situação: docs.oracle.com/javase/6/docs/api/java/lang/...
— smp7d

@ smp7d Veja a resposta do kevinarpe abaixo e seu comentário de StephenC. É um recurso muito legal, mas tem armadilhas!
— 27417 Benj

Respostas:


122

Porque não é possível lidar com essas exceções verificadas na sua fonte. Você não tem controle sobre o processo de inicialização, e os blocos estáticos {} não podem ser chamados a partir de sua fonte, para poder envolvê-los com try-catch.

Como você não pode manipular nenhum erro indicado por uma exceção verificada, foi decidido proibir a execução de blocos estáticos de exceções verificadas.

O bloco estático não deve lançar exceções verificadas, mas ainda permite que exceções desmarcadas / em tempo de execução sejam lançadas. Mas, de acordo com as razões acima, você também não seria capaz de lidar com elas.

Resumindo, essa restrição impede (ou pelo menos dificulta) o desenvolvedor de criar algo que pode resultar em erros dos quais o aplicativo não pode se recuperar.


69
Na verdade, essa resposta é imprecisa. Você pode lançar exceções em um bloco estático. O que você não pode fazer é permitir que uma exceção marcada se propague a partir de um bloco estático.
— Stephen C

16
Você PODE lidar com essa exceção, se estiver carregando uma classe dinâmica, com Class.forName (..., true, ...); É verdade que isso não é algo que você encontra com muita frequência.
— LadyCailin

2
static {throw new NullPointerExcpetion ()} - isso também não será compilado!
— Kirill Bazarov 26/03

4
@KirillBazarov uma classe com um inicializador estático que sempre resulta em uma exceção não será compilada (porque por que deveria?). Envolva essa declaração throw em uma cláusula if e você estará pronto.
— precisa saber é o seguinte

2
@ Ravisha porque, nesse caso, não há chance de o inicializador concluir normalmente em qualquer caso. Com o try-catch, pode não haver exceção lançada por println e, portanto, o inicializador pode concluir sem exceção. É o resultado incondicional de uma exceção que a torna um erro de compilação. Veja o JLS para isso: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Mas o compilador ainda pode ser enganado adicionando uma condição simples no seu caso:static { if(1 < 10) { throw new NullPointerException(); } }
— Kosi2801

67

Você pode solucionar o problema capturando qualquer exceção verificada e repetindo-a novamente como uma exceção não verificada. Esta classe de exceção desmarcada funciona bem como um invólucro: java.lang.ExceptionInInitializerError.

Código de amostra:

protected static class _YieldCurveConfigHelperSingleton {

    public static YieldCurveConfigHelper _staticInstance;

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

1
@ DK: Talvez sua versão do Java não suporte esse tipo de cláusula catch. Tente: em catch (Exception e) {vez disso.
— Kevinarpe

4
Sim, você pode fazer isso, mas é uma péssima ideia. A exceção desmarcada coloca a classe e quaisquer outras classes que dependem do estado de falha que só podem ser resolvidas descarregando as classes. Isso é tipicamente impossível, e System.exit(...)(ou equivalente) é sua única opção,
— Stephen C

1
@StephenC, podemos pensar que, se uma classe "pai" falhar ao carregar, é de fato desnecessário carregar suas classes dependentes, pois seu código não funcionará? Você poderia fornecer um exemplo de caso em que seria necessário carregar uma classe tão dependente? Graças
— Benj

Que tal ... se o código tentar carregá-lo dinamicamente; por exemplo, via Class.forName.
— Stephen C

21

Teria que se parecer com isso (este não é um código Java válido)

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

mas como o anúncio seria onde você o pegaria? As exceções marcadas requerem captura. Imagine alguns exemplos que podem inicializar a classe (ou não, porque ela já foi inicializada) e, apenas para chamar a atenção da complexidade que ela apresentaria, eu coloquei os exemplos em outro initalizador 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?
  }
}

E outra coisa desagradável -

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

Imagine que a ClassA tinha um inicializador estático lançando uma exceção verificada: nesse caso, o MyInterface (que é uma interface com um inicializador estático "oculto") teria que lançar a exceção ou manipular - manipulação de exceção em uma interface? Melhor deixar como está.


7
mainpode lançar exceções verificadas. Obviamente, esses não podem ser manipulados.
— Caracol mecânico

@ Mecanicalsnail: ponto interessante. No meu modelo mental de Java, presumo que exista um Thread.UncaughtExceptionHandler "mágico" (padrão) anexado ao thread em execução main()que imprime a exceção com rastreamento de pilha para System.erre depois chama System.exit(). No final, a resposta para essa pergunta é provavelmente: "porque os designers de Java disseram isso".
— Kevinarpe

7

Por que o Java não permite lançar uma exceção verificada de um bloco de inicialização estático?

Tecnicamente, você pode fazer isso. No entanto, a exceção verificada deve ser capturada dentro do bloco. Uma exceção verificada não tem permissão para se propagar para fora do bloco.

Tecnicamente, também é possível permitir que uma exceção desmarcada se propague a partir de um bloco inicializador estático 1 . Mas é uma péssima idéia fazer isso deliberadamente! O problema é que a própria JVM captura a exceção não verificada e a agrupa e a repete como a ExceptionInInitializerError.

NB: isso Errornão é uma exceção regular. Você não deve tentar se recuperar dele.

Na maioria dos casos, a exceção não pode ser capturada:

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)

Em nenhum lugar você pode colocar um try ... catchno acima para capturar o ExceptionInInitializerError2 .

Em alguns casos, você pode pegá-lo. Por exemplo, se você acionou a inicialização da classe chamando Class.forName(...), poderá colocar a chamada em a trye capturar a ExceptionInInitializerErrorou a subsequente NoClassDefFoundError.

No entanto, se você tentar se recuperar de um, ExceptionInInitializerErroré provável que encontre um obstáculo. O problema é que, antes de lançar o erro, a JVM marca a classe que causou o problema como "falhou". Você simplesmente não será capaz de usá-lo. Além disso, quaisquer outras classes que dependem da classe com falha também entrarão em estado de falha se tentarem inicializar. O único caminho a seguir é descarregar todas as classes com falha. Isso pode ser viável para o código 3 carregado dinamicamente , mas em geral não é.

1 - É um erro de compilação se um bloco estático lança incondicionalmente uma exceção não verificada.

2 - Você pode interceptá-lo registrando um manipulador de exceção não capturado padrão, mas isso não permitirá a recuperação, porque o encadeamento "principal" não pode ser iniciado.

3 - Se você quiser recuperar as classes com falha, precisará se livrar do carregador de classes que as carregou.


Qual foi o motivo por trás dessa decisão de design?

É para proteger o programador de escrever código que lança exceções que não podem ser manipuladas!

Como vimos, uma exceção em um inicializador estático transforma um aplicativo típico em um bloco. A melhor coisa que os designers de linguagem podem fazer é lidar com o caso verificado como um erro de compilação. (Infelizmente, não é prático fazer isso também com exceções não verificadas.)


OK, então o que você deve fazer se seu código "precisar" lançar exceções em um inicializador estático. Basicamente, existem duas alternativas:

  1. Se a recuperação (completa!) Da exceção dentro do bloco for possível, faça isso.

  2. Caso contrário, reestruture seu código para que a inicialização não ocorra em um bloco de inicialização estática (ou nos inicializadores de variáveis ​​estáticas).


Existem recomendações gerais sobre como estruturar o código para que ele não faça nenhuma inicialização estática?
— precisa saber é o seguinte


1
1) Eu não tenho nenhum. 2) Eles parecem ruins. Veja os comentários que eu deixei neles. Mas estou apenas repetindo o que disse na minha Resposta acima. Se você ler e entender minha resposta, saberá que essas "soluções" não são soluções.
— Stephen C

4

Dê uma olhada nas especificações da linguagem Java : afirma-se que é um erro de tempo de compilação se o inicializador estático falhar e conseguir concluir abruptamente com uma exceção verificada.


5
Isso não responde à pergunta, no entanto. ele perguntou por que é um erro de tempo de compilação.
— Winston Smith

Hmm, portanto, é possível lançar qualquer RuntimeError, porque o JLS menciona apenas exceções verificadas.
— precisa saber é o seguinte

É isso mesmo, mas você nunca verá como um rastreamento de pilha. É por isso que você precisa ter cuidado com os blocos de inicialização estáticos.
— EJB

2
@EJB: Isso está incorreto. Eu apenas tentei sair e o seguinte código me deu um stacktrace visual: public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }Saída: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

A parte "Causada por" mostra o rastreamento da pilha em que você provavelmente está mais interessado.
— LadyCailin 18/12/12

2

Como nenhum código que você escreve pode chamar bloco de inicialização estática, não é útil ativar a opção marcada exceptions. Se fosse possível, o que a jvm faria quando fossem lançadas exceções verificadas? Runtimeexceptionssão propagados.


1
Bem, sim, eu entendo a coisa agora. Foi muito bobo da minha parte postar uma pergunta como esta. Mas, infelizmente ... não posso excluí-lo agora. :( No entanto, +1 na sua resposta ... #
— missingfaktor

1
@fast, na verdade, as exceções verificadas NÃO são convertidas em RuntimeExceptions. Se você escrever o bytecode, poderá lançar exceções verificadas dentro de um inicializador estático para o conteúdo do seu coração. A JVM não se importa com a verificação de exceção; é puramente uma construção da linguagem Java.
— Antimony

0

Por exemplo: DispatcherServlet do Spring (org.springframework.web.servlet.DispatcherServlet) lida com o cenário que captura uma exceção verificada e lança outra exceção não verificada.

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
Isso aborda o problema de que a exceção desmarcada não pode ser capturada. Em vez disso, coloca a classe e quaisquer outras classes que dependem dela em um estado irrecuperável.
— Stephen C

@StephenC - Você poderia dar um exemplo simples em que gostaríamos de ter um estado recuperável?
— MasterJoe2

Hipoteticamente ... se você quiser recuperar da IOException para que o aplicativo possa continuar. Se você quiser fazer isso, deverá capturar a exceção e realmente lidar com ela ... não lançar uma exceção desmarcada.
— Stephen C

-5

Eu sou capaz de compilar lançando uma exceção marcada também ....

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

3
Sim, mas você está pegando dentro do bloco estático. Você não tem permissão para lançar uma exceção verificada de dentro de um bloco estático para fora dele.
— ArtOfWarfare
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.