Warum erlaubt Java nicht, eine aktivierte Ausnahme vom statischen Initialisierungsblock auszulösen?

135

Warum erlaubt Java nicht, eine aktivierte Ausnahme aus einem statischen Initialisierungsblock auszulösen? Was war der Grund für diese Designentscheidung?

fehlender Faktor
quelle
Welche Art von Ausnahme möchten Sie in welcher Situation in einem statischen Block auslösen?
Kai Huppmann
1
Ich möchte so etwas nicht tun. Ich möchte nur wissen, warum es obligatorisch ist, die aktivierten Ausnahmen innerhalb des statischen Blocks abzufangen.
fehlender Faktor
Wie würden Sie dann erwarten, dass eine aktivierte Ausnahme behandelt wird? Wenn es Sie stört, werfen Sie die abgefangene Ausnahme einfach mit der neuen RuntimeException ("Telling message", e) erneut.
Thorbjørn Ravn Andersen
18
@ ThorbjørnRavnAndersen Java bietet tatsächlich einen Ausnahmetyp für diese Situation: docs.oracle.com/javase/6/docs/api/java/lang/…
smp7d
@ smp7d Siehe Kevinvinpe Antwort unten und seinen Kommentar von StephenC. Es ist eine wirklich coole Funktion, aber es hat Fallen!
Benj

Antworten:

122

Weil es nicht möglich ist, diese geprüften Ausnahmen in Ihrer Quelle zu behandeln. Sie haben keine Kontrolle über den Initialisierungsprozess und statische {} Blöcke können nicht von Ihrer Quelle aufgerufen werden, sodass Sie sie mit try-catch umgeben können.

Da Sie keinen Fehler behandeln können, der durch eine aktivierte Ausnahme angezeigt wird, wurde beschlossen, das Auslösen von statischen Blöcken für aktivierte Ausnahmen nicht zuzulassen.

Der statische Block darf keine aktivierten Ausnahmen auslösen, ermöglicht jedoch weiterhin das Auslösen von nicht aktivierten / Laufzeitausnahmen. Aber aus den oben genannten Gründen wären Sie auch nicht in der Lage, damit umzugehen.

Zusammenfassend lässt sich sagen, dass diese Einschränkung den Entwickler daran hindert (oder zumindest erschwert), etwas zu erstellen, das zu Fehlern führen kann, von denen die Anwendung keine Wiederherstellung durchführen kann.

Kosi2801
quelle
69
Eigentlich ist diese Antwort ungenau. Sie können Ausnahmen in einem statischen Block auslösen. Was Sie nicht tun können, ist zuzulassen, dass sich eine aktivierte Ausnahme aus einem statischen Block ausbreitet .
Stephen C
16
Sie können diese Ausnahme mit Class.forName (..., true, ...) behandeln, wenn Sie selbst dynamische Klassen laden. Zugegeben, das ist etwas, auf das man nicht sehr oft stößt.
LadyCailin
2
static {throw new NullPointerExcpetion ()} - dies wird auch nicht kompiliert!
Kirill Bazarov
4
@KirillBazarov Eine Klasse mit einem statischen Initialisierer, der immer zu einer Ausnahme führt, wird nicht kompiliert (denn warum sollte das so sein?). Schließen Sie diese throw-Anweisung in eine if-Klausel ein, und Sie können loslegen.
Kallja
2
@ Ravisha, da in diesem Fall der Initialisierer in keinem Fall normal abgeschlossen werden kann. Mit dem try-catch wird möglicherweise keine Ausnahme von println ausgelöst, und daher hat der Initialisierer die Möglichkeit, ausnahmslos abzuschließen. Es ist das bedingungslose Ergebnis einer Ausnahme, die es zu einem Kompilierungsfehler macht. Siehe dazu das JLS: docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.7 Der Compiler kann sich jedoch dennoch täuschen lassen, indem er in Ihrem Fall eine einfache Bedingung hinzufügt:static { if(1 < 10) { throw new NullPointerException(); } }
Kosi2801
67

Sie können das Problem umgehen, indem Sie eine aktivierte Ausnahme abfangen und als nicht aktivierte Ausnahme erneut auslösen. Diese nicht aktivierte Ausnahmeklasse eignet sich gut als Wrapper : java.lang.ExceptionInInitializerError.

Beispielcode:

protected static class _YieldCurveConfigHelperSingleton {

    public static YieldCurveConfigHelper _staticInstance;

    static {
        try {
            _staticInstance = new YieldCurveConfigHelper();
        }
        catch (IOException | SAXException | JAXBException e) {
            throw new ExceptionInInitializerError(e);
        }
    }
}
Kevinarpe
quelle
1
@DK: Möglicherweise unterstützt Ihre Java-Version diese Art von catch-Klausel nicht. Versuchen Sie: catch (Exception e) {stattdessen.
Kevinarpe
4
Ja, das können Sie, aber es ist eine wirklich schlechte Idee. Die nicht aktivierte Ausnahme versetzt die Klasse und alle anderen Klassen, die von ihrem fehlgeschlagenen Status abhängen, in einen Zustand, der nur durch Entladen der Klassen behoben werden kann. Das ist normalerweise unmöglich und System.exit(...)(oder gleichwertig) ist Ihre einzige Option
Stephen C
1
@StephenC können wir denken, dass wenn eine "übergeordnete" Klasse nicht geladen werden kann, es de facto unnötig ist, ihre abhängigen Klassen zu laden, da Ihr Code nicht funktioniert? Könnten Sie bitte ein Beispiel für einen Fall nennen, in dem es sowieso notwendig wäre, eine solche abhängige Klasse zu laden? Danke
Benj
Wie wäre es ... wenn der Code versucht, ihn dynamisch zu laden; zB über Class.forName.
Stephen C
21

Es müsste so aussehen (dies ist kein gültiger Java-Code)

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

aber wie würde Werbung, wo Sie es fangen? Überprüfte Ausnahmen erfordern das Fangen. Stellen Sie sich einige Beispiele vor, die die Klasse initialisieren können (oder nicht, weil sie bereits initialisiert ist), und um die Aufmerksamkeit auf die Komplexität zu lenken, die sie einführen würde, füge ich die Beispiele in einen anderen statischen Initialisierer ein:

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?
  }
}

Und noch eine böse Sache -

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

Stellen Sie sich vor, ClassA hätte einen statischen Initialisierer, der eine aktivierte Ausnahme auslöst: In diesem Fall müsste MyInterface (eine Schnittstelle mit einem 'versteckten' statischen Initialisierer) die Ausnahme auslösen oder behandeln - Ausnahmebehandlung an einer Schnittstelle? Lass es besser so wie es ist.

Andreas Dolk
quelle
7
mainkann geprüfte Ausnahmen auslösen. Offensichtlich können diese nicht gehandhabt werden.
Mechanische Schnecke
@ Mechanicalnail: Interessanter Punkt. In meinem mentalen Java-Modell gehe ich davon aus, main()dass an den laufenden Thread ein "magischer" (Standard-) Thread.UncaughtExceptionHandler angehängt ist, der die Ausnahme mit Stack-Trace an druckt System.errund dann aufruft System.exit(). Am Ende lautet die Antwort auf diese Frage wahrscheinlich: "Weil Java-Designer dies gesagt haben".
Kevinarpe
7

Warum erlaubt Java nicht, eine aktivierte Ausnahme aus einem statischen Initialisierungsblock auszulösen?

Technisch können Sie dies tun. Die aktivierte Ausnahme muss jedoch innerhalb des Blocks abgefangen werden. Eine aktivierte Ausnahme darf sich nicht aus dem Block heraus verbreiten.

Technisch ist es auch möglich, zuzulassen, dass sich eine ungeprüfte Ausnahme aus einem statischen Initialisierungsblock 1 ausbreitet . Aber es ist eine wirklich schlechte Idee, dies absichtlich zu tun! Das Problem ist, dass die JVM selbst die ungeprüfte Ausnahme abfängt, sie umschließt und als ExceptionInInitializerError.

NB: Das ist Errorkeine reguläre Ausnahme. Sie sollten nicht versuchen, sich davon zu erholen.

In den meisten Fällen kann die Ausnahme nicht abgefangen werden:

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)

Es gibt keinen Ort, try ... catchan dem Sie ein oben platzieren können, um die ExceptionInInitializerError2 zu fangen .

In einigen Fällen können Sie es fangen. Wenn Sie beispielsweise die Klasseninitialisierung durch Aufrufen ausgelöst haben Class.forName(...), können Sie den Aufruf in a einschließen tryund entweder den ExceptionInInitializerErroroder einen nachfolgenden abfangen NoClassDefFoundError.

Wenn Sie jedoch versuchen, sich von einem zu erholen, könnenExceptionInInitializerError Sie auf eine Straßensperre stoßen. Das Problem ist, dass die JVM vor dem Auslösen des Fehlers die Klasse, die das Problem verursacht hat, als "fehlgeschlagen" markiert. Sie werden es einfach nicht benutzen können. Darüber hinaus werden alle anderen Klassen, die von der fehlgeschlagenen Klasse abhängen, ebenfalls in den Status "Fehlgeschlagen" versetzt, wenn sie versuchen, eine Initialisierung durchzuführen. Die einzige Möglichkeit besteht darin, alle fehlgeschlagenen Klassen zu entladen. Dies ist möglicherweise für dynamisch geladenen Code 3 möglich , im Allgemeinen jedoch nicht.

1 - Es ist ein Kompilierungsfehler, wenn ein statischer Block bedingungslos eine ungeprüfte Ausnahme auslöst.

2 - Möglicherweise können Sie es abfangen, indem Sie einen standardmäßigen nicht erfassten Ausnahmebehandler registrieren. Dadurch können Sie ihn jedoch nicht wiederherstellen, da Ihr "Haupt" -Thread nicht gestartet werden kann.

3 - Wenn Sie die fehlgeschlagenen Klassen wiederherstellen möchten, müssen Sie den Klassenlader entfernen, der sie geladen hat.


Was war der Grund für diese Designentscheidung?

Es soll den Programmierer vor dem Schreiben von Code schützen, der Ausnahmen auslöst, die nicht behandelt werden können!

Wie wir gesehen haben, verwandelt eine Ausnahme in einem statischen Initialisierer eine typische Anwendung in einen Baustein. Das Beste, was die Sprachdesigner tun könnten, ist, den geprüften Fall als Kompilierungsfehler zu behandeln. (Leider ist es nicht praktikabel, dies auch für ungeprüfte Ausnahmen zu tun.)


OK, was sollten Sie also tun, wenn Ihr Code Ausnahmen in einem statischen Initialisierer "auslösen" muss? Grundsätzlich gibt es zwei Alternativen:

  1. Wenn eine (vollständige!) Wiederherstellung von der Ausnahme innerhalb des Blocks möglich ist, tun Sie dies.

  2. Andernfalls strukturieren Sie Ihren Code so um, dass die Initialisierung nicht in einem statischen Initialisierungsblock (oder in den Initialisierern statischer Variablen) erfolgt.

Stephen C.
quelle
Gibt es allgemeine Empfehlungen zur Strukturierung von Code, damit keine statische Initialisierung durchgeführt wird?
MasterJoe2
1
1) Ich habe keine. 2) Sie klingen schlecht. Siehe die Kommentare, die ich dazu hinterlassen habe. Aber ich wiederhole nur das, was ich oben in meiner Antwort gesagt habe. Wenn Sie meine Antwort lesen und verstehen, wissen Sie, dass diese "Lösungen" keine Lösungen sind.
Stephen C
4

Schauen Sie sich die Java-Sprachspezifikationen an : Es wird angegeben, dass es sich um einen Fehler bei der Kompilierung handelt, wenn der statische Initialisierer fehlschlägt und mit einer aktivierten Ausnahme abrupt abgeschlossen werden kann .

Laurent Etiemble
quelle
5
Dies beantwortet die Frage jedoch nicht. Er fragte, warum es sich um einen Kompilierungsfehler handelt.
Winston Smith
Hmm, daher sollte es möglich sein, einen RuntimeError auszulösen, da JLS nur geprüfte Ausnahmen erwähnt.
Andreas Dolk
Das ist richtig, aber Sie werden es nie als Stacktrace sehen. Aus diesem Grund müssen Sie mit statischen Initialisierungsblöcken vorsichtig sein.
EJB
2
@EJB: Das ist falsch. Ich habe es gerade ausprobiert und der folgende Code gab mir eine visuelle Stapelverfolgung: public class Main { static { try{Class.forName("whathappenswhenastaticblockthrowsanexception");} catch (ClassNotFoundException e){throw new RuntimeException(e);} } public static void main(String[] args){} }Ausgabe: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
Der Teil "Verursacht durch" zeigt die Stapelverfolgung, an der Sie wahrscheinlich mehr interessiert sind.
LadyCailin
2

Da kein Code, den Sie schreiben, einen statischen Initialisierungsblock aufrufen kann, ist es nicht sinnvoll, aktiviert zu werfen exceptions. Wenn es möglich wäre, was würde der JVM tun, wenn geprüfte Ausnahmen ausgelöst werden? Runtimeexceptionsverbreitet werden.

fastcodejava
quelle
1
Nun ja, ich verstehe die Sache jetzt. Es war sehr dumm von mir, eine solche Frage zu stellen. Aber leider ... ich kann es jetzt nicht löschen. :( Trotzdem +1 für Ihre Antwort ...
fehlender Faktor
1
@fast, Tatsächlich werden geprüfte Ausnahmen NICHT in RuntimeExceptions konvertiert. Wenn Sie selbst Bytecode schreiben, können Sie nach Herzenslust markierte Ausnahmen in einen statischen Initialisierer werfen. Die JVM kümmert sich überhaupt nicht um die Ausnahmeprüfung. Es ist ein reines Java-Sprachkonstrukt.
Antimon
0

Beispiel: Das DispatcherServlet von Spring (org.springframework.web.servlet.DispatcherServlet) behandelt das Szenario, das eine aktivierte Ausnahme abfängt und eine weitere nicht aktivierte Ausnahme auslöst.

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
quelle
1
Dies nähert sich dem Problem, dass die ungeprüfte Ausnahme nicht abgefangen werden kann. Stattdessen werden die Klasse und alle anderen davon abhängigen Klassen in einen nicht wiederherstellbaren Zustand versetzt.
Stephen C
@StephenC - Könnten Sie bitte ein einfaches Beispiel geben, in dem wir einen wiederherstellbaren Zustand haben möchten?
MasterJoe2
Hypothetisch ... wenn Sie in der Lage sein möchten, sich von der IOException zu erholen, damit die Anwendung fortgesetzt werden kann. Wenn Sie dies tun möchten, müssen Sie die Ausnahme abfangen und tatsächlich behandeln ... und keine ungeprüfte Ausnahme auslösen.
Stephen C
-5

Ich kann das Kompilieren einer aktivierten Ausnahme kompilieren. Auch ....

static {
    try {
        throw new IOException();
    } catch (Exception e) {
         // Do Something
    }
}
user2775569
quelle
3
Ja, aber Sie fangen es innerhalb des statischen Blocks auf. Sie dürfen eine aktivierte Ausnahme nicht von innerhalb eines statischen Blocks nach außen werfen.
ArtOfWarfare