Pourquoi Java ne permet-il pas de lever une exception vérifiée à partir d'un bloc d'initialisation statique?

135

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?

manquant
la source
Quel genre d'exception aimeriez-vous lancer dans quel genre de situation dans un bloc statique?
Kai Huppmann le
1
Je ne veux rien faire de tel. Je veux juste savoir pourquoi il est obligatoire d'attraper les exceptions vérifiées à l'intérieur du bloc statique.
missingfaktor
Comment vous attendez-vous à ce qu'une exception vérifiée soit traitée alors? Si cela vous dérange, relancez simplement l'exception interceptée avec throw new RuntimeException ("Telling message", e);
Thorbjørn Ravn Andersen du
18
@ ThorbjørnRavnAndersen Java fournit en fait un type d'exception pour cette situation: docs.oracle.com/javase/6/docs/api/java/lang/…
smp7d
@ smp7d Voir la réponse de kevinarpe ci-dessous, et son commentaire de StephenC. C'est une fonctionnalité vraiment cool mais elle a des pièges!
Benj

Réponses:

122

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.

Kosi2801
la source
69
En fait, cette réponse est inexacte. Vous POUVEZ lancer des exceptions dans un bloc statique. Ce que vous ne pouvez pas faire, c'est autoriser une exception vérifiée à se propager hors d' un bloc statique.
Stephen C
16
Vous POUVEZ gérer cette exception, si vous effectuez vous-même un chargement de classe dynamique, avec Class.forName (..., true, ...); Certes, ce n'est pas quelque chose que vous rencontrez très souvent.
LadyCailin
2
static {throw new NullPointerExcpetion ()} - cela ne compilera pas non plus!
Kirill Bazarov
4
@KirillBazarov une classe avec un initialiseur statique qui aboutit toujours à une exception ne se compilera pas (car pourquoi le devrait-elle?). Enveloppez cette instruction throw dans une clause if et vous êtes prêt à partir.
Kallja
2
@Ravisha car dans ce cas, il n'y a aucune chance pour l'initialiseur de se terminer normalement dans tous les cas. Avec le try-catch, il se peut qu'aucune exception ne soit levée par println et par conséquent, l'initialiseur a une chance de se terminer sans exception. C'est le résultat inconditionnel d'une exception qui en fait une erreur de compilation. Voir le JLS pour cela: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Mais le compilateur peut encore être trompé en ajoutant une condition simple dans votre cas:static { if(1 < 10) { throw new NullPointerException(); } }
Kosi2801
67

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);
        }
    }
}
kevinarpe
la source
1
@DK: Peut-être que votre version de Java ne prend pas en charge ce type de clause catch. Essayez: à la catch (Exception e) {place.
kevinarpe
4
Oui, vous pouvez le faire, mais c'est une très mauvaise idée. L'exception non vérifiée place la classe et toutes les autres classes qui en dépendent dans un état d' échec qui ne peut être résolu qu'en déchargeant les classes. C'est généralement impossible, et System.exit(...)(ou équivalent) est votre seule option,
Stephen C
1
@StephenC peut-on penser que si une classe "parent" échoue à se charger, il est de facto inutile de charger ses classes dépendantes puisque votre code ne fonctionnera pas? Pourriez-vous donner un exemple de cas où il serait de toute façon nécessaire de charger une telle classe dépendante? Merci
Benj
Que diriez-vous ... si le code essaie de le charger dynamiquement; par exemple via Class.forName.
Stephen C
21

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.

Andreas Dolk
la source
7
mainpeut lever des exceptions vérifiées. De toute évidence, ceux-ci ne peuvent pas être traités.
Escargot mécanique
@Mechanicalsnail: Point intéressant. Dans mon modèle mental de Java, je suppose qu'il existe un Thread.UncaughtExceptionHandler "magique" (par défaut) attaché au thread en cours d'exécution 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".
kevinarpe le
7

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 Errorpas 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 ... catchdans ce qui précède pour attraper le ExceptionInInitializerError2 .

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 tryet attraper le ExceptionInInitializerErrorou un suivant NoClassDefFoundError.

Cependant, si vous tentez de vous remettre d'un ExceptionInInitializerErrorproblè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:

  1. Si une récupération (complète!) De l'exception dans le bloc est possible, faites-le.

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

Stephen C
la source
Existe-t-il des recommandations générales sur la façon de structurer le code afin qu'il n'effectue aucune initialisation statique?
MasterJoe
Comment ces solutions sonnent-elles? stackoverflow.com/a/21321935/6648326 et stackoverflow.com/a/56575807/6648326
MasterJoe
1
1) Je n'en ai pas. 2) Ils sonnent mal. Voir les commentaires que j'ai laissés sur eux. Mais je ne fais que répéter ce que j'ai dit dans ma réponse ci-dessus. Si vous lisez et comprenez ma réponse, vous saurez que ces «solutions» ne sont pas des solutions.
Stephen C
4

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.

Laurent Etiemble
la source
5
Cela ne répond cependant pas à la question. il a demandé pourquoi il s'agissait d'une erreur de compilation.
Winston Smith
Hmm, donc lancer n'importe quelle RuntimeError devrait être possible, car JLS ne mentionne que les exceptions vérifiées.
Andreas Dolk
C'est vrai, mais vous ne verrez jamais comme un stacktrace. C'est pourquoi vous devez faire attention aux blocs d'initialisation statiques.
EJB
2
@EJB: C'est incorrect. Je viens de l'essayer et le code suivant m'a donné un stacktrace visuel: 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...
Konrad Höffner
La partie "Caused by" montre la trace de pile qui vous intéresse probablement plus.
LadyCailin
2

É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? Runtimeexceptionsse propagent.

fastcodejava
la source
1
Eh bien, oui je comprends la chose maintenant. C'était très idiot de ma part de poster une question comme celle-ci. Mais hélas ... je ne peux pas le supprimer maintenant. :( Néanmoins, +1 pour votre réponse ...
missingfaktor
1
@fast, En fait, les exceptions vérifiées ne sont PAS converties en exceptions d'exécution. Si vous écrivez vous-même le bytecode, vous pouvez lancer des exceptions vérifiées dans un initialiseur statique à votre guise. La JVM ne se soucie pas du tout de la vérification des exceptions; c'est purement une construction en langage Java.
Antimoine
0

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());
    }
pcdhan
la source
1
Cette approche le problème que l'exception non vérifiée ne peut pas être interceptée. Au lieu de cela, il met la classe et toutes les autres classes qui en dépendent dans un état irrécupérable.
Stephen C
@StephenC - Pourriez-vous s'il vous plaît donner un exemple simple dans lequel nous voudrions avoir un état récupérable?
MasterJoe
Hypothétiquement ... si vous vouliez pouvoir récupérer de l'exception IOException pour que l'application puisse continuer. Si vous voulez faire cela, vous devez attraper l'exception et la gérer réellement ... ne pas lever une exception non vérifiée.
Stephen C
-5

Je suis capable de compiler en lançant une exception cochée également ....

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}
user2775569
la source
3
Ouais, mais vous l'attrapez dans le bloc statique. Vous n'êtes pas autorisé à lancer une exception vérifiée de l'intérieur d'un bloc statique vers l'extérieur.
ArtOfWarfare