web-dev-qa-db-fra.com

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

Pourquoi Java ne permet-il pas de lever une exception vérifiée à partir d'un bloc d'initialisation statique? Quelle est la raison de cette décision de conception?

125
missingfaktor

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 statiques {} ne peuvent pas être appelés depuis votre source afin de pouvoir 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 le lancement de blocs statiques d'exceptions vérifiées.

Le bloc statique ne doit pas lancer coché exceptions, mais permet néanmoins de lever les exceptions non vérifiées/d'exécution. Mais selon les raisons ci-dessus, vous ne pourrez pas les gérer non plus.

Pour résumer, cette restriction empêche (ou du moins complique la tâche) le développeur de créer quelque chose qui pourrait entraîner des erreurs que l'application ne pourrait pas récupérer.

109
Kosi2801

Vous pouvez contourner le problème en interceptant toute exception vérifiée et en la réexécutant en tant qu'exception non contrôlée. Cette classe d’exception non contrôlée fonctionne bien en tant que wrapper: 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);
        }
    }
}
65
kevinarpe

Il devrait ressembler à ceci (c'est not valide Java code)

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

mais comment ferais-tu l'annonce où tu l'attrapes? Les exceptions vérifiées nécessitent une capture. Imaginez quelques exemples qui pourraient initialiser la classe (ou pas parce qu'elle est déjà initialisée), et juste pour attirer l'attention sur la complexité que cela 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 méchante -

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

Imagine ClassA avait un initialiseur statique qui lançait une exception vérifiée: dans ce cas, MyInterface (une interface avec un initialiseur statique "masqué") devrait lever ou gérer l’exception - le traitement des exceptions sur une interface? Mieux vaut le laisser tel quel.

19
Andreas_D

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 à propager en dehors du bloc.

Techniquement, il est également possible de permettre à une exception non contrôlée de se propager à partir d'un bloc d'initialiseur statique1. Mais c'est une très mauvaise idée de le faire délibérément! Le problème est que la JVM elle-même attrape l'exception non vérifiée, l'enveloppe et la retranscrit sous la forme d'un ExceptionInInitializerError.

NB: il s’agit d’un Error pas une exception régulière. Vous ne devriez pas essayer de vous en remettre.

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’ya nulle part où vous pouvez placer un try ... catch Dans 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 inclure l'appel dans un try et intercepter le ExceptionInInitializerError ou un suivant NoClassDefFoundError.

Cependant, si vous essayez de récupérer à partir d'un ExceptionInInitializerError, vous risquez de vous heurter à un obstacle. Le problème est qu'avant de générer l'erreur, la machine virtuelle Java marque la classe qui a causé le problème comme "a échoué". Vous ne pourrez tout simplement pas l'utiliser. De plus, toutes les autres classes dépendant de la classe ayant échoué passeront à l'état d'échec si elles tentent de s'initialiser. La seule façon d'avancer consiste à décharger toutes les classes ayant échoué. Cela pourrait être réalisable pour du code chargé dynamiquement3mais en général ça ne l’est pas.

1 - C’est une erreur de compilation si un bloc statique sans condition lève une exception non contrôlée.

2 - Vous pourriez pouvoir l'intercepter en enregistrant un gestionnaire d'exceptions non capturé par défaut, mais cela ne vous permettra pas de récupérer, car votre "main" le fil ne peut pas commencer.

3 - Si vous voulez récupérer les classes en échec, vous devez vous débarrasser du chargeur de classes qui les a chargés.


Quelle était la raison derrière 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 une brique. La meilleure chose à faire que les concepteurs de langage pourraient faire est de traiter le cas coché comme une erreur de compilation. (Malheureusement, il n'est pas pratique de faire cela pour les exceptions non vérifiées également.)


OK, que devez-vous faire si votre code "a besoin" de générer des exceptions dans un initialiseur statique. Fondamentalement, il existe deux alternatives:

  1. Si la 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).

7
Stephen C

Jetez un coup d'œil au Spécifications du langage Java : il est indiqué qu'il s'agit d'une erreur de compilation lors d'un initialiseur statique échoue  peut s'achever brusquement avec une exception vérifiée.

4
Laurent Etiemble

Comme aucun code que vous écrivez ne peut appeler un bloc d’initialisation statique, il n’est pas utile de lancer le fichier vérifié exceptions. Si c'était possible, que ferait-il lorsque des exceptions vérifiées sont levées? Runtimeexceptions sont propagés vers le haut.

2
fastcodejava

Par exemple: DispatcherServlet de Spring (org.springframework.web.servlet.DispatcherServlet) gère le scénario qui intercepte une exception contrôlée et lève une autre exception non contrôlé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());
    }
0
pcdhan