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?
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?
Respostas:
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.
static { if(1 < 10) { throw new NullPointerException(); } }
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);
}
}
}
catch (Exception e) {
vez disso.
System.exit(...)
(ou equivalente) é sua única opção,
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á.
main
pode lançar exceções verificadas. Obviamente, esses não podem ser manipulados.
main()
que imprime a exceção com rastreamento de pilha para System.err
e depois chama System.exit()
. No final, a resposta para essa pergunta é provavelmente: "porque os designers de Java disseram isso".
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 Error
nã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 ... catch
no acima para capturar o ExceptionInInitializerError
2 .
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 try
e capturar a ExceptionInInitializerError
ou 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:
Se a recuperação (completa!) Da exceção dentro do bloco for possível, faça isso.
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).
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.
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...
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? Runtimeexceptions
são propagados.
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());
}
Eu sou capaz de compilar lançando uma exceção marcada também ....
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}