Pourquoi Java ne permet-il pas de lever une exception vérifiée à partir d'un bloc d'initialisation statique? Quelle était la raison de cette décision de conception?
Pourquoi Java ne permet-il pas de lever une exception vérifiée à partir d'un bloc d'initialisation statique? Quelle était la raison de cette décision de conception?
Réponses:
Parce qu'il n'est pas possible de gérer ces exceptions vérifiées dans votre source. Vous n'avez aucun contrôle sur le processus d'initialisation et les blocs static {} ne peuvent pas être appelés depuis votre source afin que vous puissiez les entourer de try-catch.
Étant donné que vous ne pouvez gérer aucune erreur indiquée par une exception vérifiée, il a été décidé d'interdire la levée de blocs statiques d'exceptions vérifiées.
Le bloc statique ne doit pas lancer d' exceptions vérifiées mais autorise toujours la levée d'exceptions non vérifiées / d'exécution. Mais selon les raisons ci-dessus, vous ne pourriez pas non plus les gérer.
Pour résumer, cette restriction empêche (ou du moins la rend plus difficile pour) le développeur de créer quelque chose qui peut entraîner des erreurs dont l'application ne pourrait pas récupérer.
static { if(1 < 10) { throw new NullPointerException(); } }
Vous pouvez contourner le problème en interceptant toute exception vérifiée et en la renvoyant en tant qu'exception non vérifiée. Cette classe d'exception non contrôlée fonctionne bien comme un emballage: java.lang.ExceptionInInitializerError
.
Exemple de code:
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) {
place.
System.exit(...)
(ou équivalent) est votre seule option,
Cela devrait ressembler à ceci (ce n'est pas du code Java valide)
// Not a valid Java Code
static throws SomeCheckedException {
throw new SomeCheckedException();
}
mais comment annoncer où vous l'attrapez? Les exceptions vérifiées doivent être capturées. Imaginez quelques exemples qui peuvent initialiser la classe (ou non parce qu'elle est déjà initialisée), et juste pour attirer l'attention sur la complexité de ce qu'elle introduirait, je mets les exemples dans un autre initalizer statique:
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?
}
}
Et une autre chose désagréable -
interface MyInterface {
final static ClassA a = new ClassA();
}
Imaginez que ClassA ait un initialiseur statique lançant une exception vérifiée: Dans ce cas, MyInterface (qui est une interface avec un initialiseur statique «caché») devrait lever l'exception ou la gérer - gestion des exceptions à une interface? Mieux vaut le laisser tel quel.
main
peut lever des exceptions vérifiées. De toute évidence, ceux-ci ne peuvent pas être traités.
main()
qui imprime l'exception avec la trace de la pile à System.err
, puis appelle System.exit()
. En fin de compte, la réponse à cette question est probablement: "parce que les concepteurs Java l'ont dit".
Pourquoi Java ne permet-il pas de lever une exception vérifiée à partir d'un bloc d'initialisation statique?
Techniquement, vous pouvez le faire. Cependant, l'exception vérifiée doit être interceptée dans le bloc. Une exception vérifiée n'est pas autorisée à se propager hors du bloc.
Techniquement, il est également possible d'autoriser une exception non vérifiée à se propager à partir d'un bloc d'initialisation statique 1 . Mais c'est vraiment une mauvaise idée de le faire délibérément! Le problème est que la machine virtuelle Java elle-même intercepte l'exception non vérifiée, l'encapsule et la renvoie en tant que fichier ExceptionInInitializerError
.
NB: ce n'est Error
pas une exception régulière. Vous ne devez pas tenter de récupérer.
Dans la plupart des cas, l'exception ne peut pas être interceptée:
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)
Il n'y a nulle part où vous pouvez placer un try ... catch
dans ce qui précède pour attraper le ExceptionInInitializerError
2 .
Dans certains cas, vous pouvez l'attraper. Par exemple, si vous avez déclenché l'initialisation de la classe en appelant Class.forName(...)
, vous pouvez placer l'appel dans un try
et attraper le ExceptionInInitializerError
ou un suivant NoClassDefFoundError
.
Cependant, si vous tentez de vous remettre d'un ExceptionInInitializerError
problème, vous risquez de vous heurter à un barrage routier. Le problème est qu'avant de lancer l'erreur, la machine virtuelle Java marque la classe à l'origine du problème comme "échouée". Vous ne pourrez tout simplement pas l'utiliser. De plus, toutes les autres classes qui dépendent de la classe ayant échoué passeront également en état d'échec si elles tentent de s'initialiser. La seule façon d'avancer est de décharger toutes les classes ayant échoué. Cela pourrait être faisable pour le code 3 chargé dynamiquement , mais en général ce n'est pas le cas.
1 - C'est une erreur de compilation si un bloc statique lève sans condition une exception non vérifiée.
2 - Vous pourrez peut- être l'intercepter en enregistrant un gestionnaire d'exceptions non interceptées par défaut, mais cela ne vous permettra pas de récupérer, car votre thread "principal" ne peut pas démarrer.
3 - Si vous vouliez récupérer les classes ayant échoué, vous devrez vous débarrasser du chargeur de classes qui les a chargées.
Quelle était la raison de cette décision de conception?
C'est pour protéger le programmeur de l'écriture de code qui lève des exceptions qui ne peuvent pas être gérées!
Comme nous l'avons vu, une exception dans un initialiseur statique transforme une application typique en brique. La meilleure chose que les concepteurs de langage pourraient faire est de traiter la casse cochée comme une erreur de compilation. (Malheureusement, il n'est pas pratique de le faire également pour les exceptions non vérifiées.)
OK, alors que devez-vous faire si votre code "a besoin" de lever des exceptions dans un initialiseur statique. Fondamentalement, il existe deux alternatives:
Si une récupération (complète!) De l'exception dans le bloc est possible, faites-le.
Sinon, restructurez votre code afin que l'initialisation ne se produise pas dans un bloc d'initialisation statique (ou dans les initialiseurs de variables statiques).
Jetez un œil aux spécifications du langage Java : il est indiqué qu'il s'agit d'une erreur de compilation si l'initialiseur statique échoue, il est capable de se terminer brusquement avec une exception vérifiée.
public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }
Sortie: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...
Étant donné qu'aucun code que vous écrivez ne peut appeler un bloc d'initialisation statique, il n'est pas utile de lancer vérifié exceptions
. Si c'était possible, que ferait le jvm lorsqu'une exception vérifiée est levée? Runtimeexceptions
se propagent.
Par exemple: le DispatcherServlet de Spring (org.springframework.web.servlet.DispatcherServlet) gère le scénario qui intercepte une exception vérifiée et lève une autre exception non vérifiée.
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());
}
Je suis capable de compiler en lançant une exception cochée également ....
static {
try {
throw new IOException();
} catch (Exception e) {
// Do Something
}
}