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?

desaparecido
fonte
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.

Kosi2801
fonte
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);
        }
    }
}
kevinarpe
fonte
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á.

Andreas Dolk
fonte
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).

Stephen C
fonte
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.

Laurent Etiemble
fonte
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.

fastcodejava
fonte
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());
    }
pcdhan
fonte
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
    }
}
user2775569
fonte
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