Warum können wir nicht über nicht initialisierte lokale Variablen auf statische Inhalte zugreifen?

77

Schauen Sie sich den folgenden Code an:

class Foo{
    public static int x = 1;
}

class Bar{    
    public static void main(String[] args) {
        Foo foo;
        System.out.println(foo.x); // Error: Variable 'foo' might not have been initialized
    }
}

Wie Sie sehen , während für den Zugriff statischen Feldes versuchen xüber einen nicht initialisierte lokalen Variable Foo foo;Code foo.xerzeugt Übersetzungsfehler: Variable 'foo' might not have been initialized.

Es könnte scheinen , wie dieser Fehler macht Sinn, aber nur , bis wir erkennen , dass ein Zugriff auf staticMitglied die JVM nicht wirklich verwendet den Wert einer Variablen, sondern nur seine Art .

Zum Beispiel kann ich foomit value initialisieren nullund dies ermöglicht uns den xproblemlosen Zugriff :

Foo foo = null;
System.out.println(foo.x); //compiles and at runtime prints 1!!! 

Ein solches Szenario funktioniert, weil der Compiler erkennt, dass xes statisch ist und so behandelt, foo.xwie es geschrieben wurde Foo.x(zumindest dachte ich das bis jetzt).

Warum besteht der Compiler plötzlich darauf foo, einen Wert zu haben, den er überhaupt nicht verwendet ?


Haftungsausschluss: Dies ist kein Code, der in einer realen Anwendung verwendet werden würde, sondern ein interessantes Phänomen, auf das ich bei Stack Overflow keine Antwort finden konnte. Deshalb habe ich mich entschlossen, danach zu fragen.

Pshemo
quelle
7
Ich würde sagen, eine Einschränkung im Compiler, die es nicht wirklich wert ist, behoben zu werden, da der Code sowieso eine Warnung auslöst.
M Anouti
1
@manouti Das ist auch meine Vermutung, aber ich bin immer noch daran interessiert, warum sich der Compiler so verhält. Welcher Teil der Spezifikation erzwingt es?
Pshemo
9
@portfoliobuilder Hier besteht kein NPE-Risiko, da, wie in Frage erwähnt, beim Zugriff auf den staticMember-Compiler nicht der Wert der Variablen, sondern deren Typ verwendet wird . Wir können sogar schreiben ((Foo)null).xund dies wird kompiliert und funktioniert, da der Compiler erkennt, dass dies xstatisch ist (es sei denn, ich habe Ihren Kommentar falsch verstanden).
Pshemo
29
Der Zugriff auf eine statische Variable aus einem nicht statischen Kontext (z. B. foo.x) sollte ein Compilerfehler gewesen sein, als Java zum ersten Mal erstellt wurde. Leider ist dieses Schiff vor über 25 Jahren gesegelt und wäre eine bahnbrechende Veränderung, wenn sie es jetzt ändern würden.
Powerlord
2
@portfoliobuilder "..es gibt es absolut Risiko" Welches Risiko haben Sie im Sinn? Auch ein nitpick: sind beide Wege technisch korrekt (leider), aber Foo.xist bevorzugt (weshalb wir in der Regel Kompilation bekommen Warnung , wenn wir verwenden Variante versuchen foo.x).
Pshemo

Antworten:

74

§15.11. Feldzugriffsausdrücke :

Wenn das Feld statisch ist :

Der primäre Ausdruck wird ausgewertet und das Ergebnis verworfen . Wenn die Auswertung des primären Ausdrucks abrupt abgeschlossen wird, wird der Feldzugriffsausdruck aus demselben Grund abrupt abgeschlossen.

Wo früher angegeben, dass der Feldzugriff durch identifiziert wird Primary.Identifier.

Dies zeigt, dass, obwohl es das nicht zu verwenden scheint Primary, es dennoch ausgewertet wird und das Ergebnis dann verworfen wird, weshalb es initialisiert werden muss. Dies kann einen Unterschied machen, wenn die Auswertung den Zugriff wie im Angebot angegeben anhält.

BEARBEITEN:

Hier ist ein kurzes Beispiel, um nur visuell zu demonstrieren, dass Primarydas Ergebnis ausgewertet wird, obwohl das Ergebnis verworfen wird:

class Foo {
    public static int x = 1;
    
    public static Foo dummyFoo() throws InterruptedException {
        Thread.sleep(5000);
        return null;
    }
    
    public static void main(String[] args) throws InterruptedException {
        System.out.println(dummyFoo().x);
        System.out.println(Foo.x);
    }
}

Hier sehen Sie, dass dies dummyFoo()immer noch ausgewertet wird, da sich das printum 5 Sekunden verzögert Thread.sleep(), obwohl immer ein nullWert zurückgegeben wird, der verworfen wird.

Wenn der Ausdruck nicht ausgewertet wurde print, wird er sofort angezeigt. Dies wird angezeigt, wenn die Klasse Foodirekt für den Zugriff xmit verwendet wird Foo.x.

Hinweis: Der Methodenaufruf wird auch Primaryin §15.8 Primäre Ausdrücke gezeigt .

Nexevis
quelle
4
Interessanterweise javacwird dies wörtlich, indem eine Lade- und Pop-Anweisung generiert wird, während ecjdie formale Regel erzwungen wird, dh kein Zugriff über eine nicht initialisierte Variable zugelassen wird, aber kein Code für die nebenwirkungsfreie Operation generiert wird.
Holger
21

Kapitel 16. Bestimmte Zuordnung

Jede lokale Variable (§14.4) und jedes leere Endfeld (§4.12.4, §8.3.1.2) muss einen definitiv zugewiesenen Wert haben, wenn auf ihren Wert zugegriffen wird .

Es spielt keine Rolle, auf was Sie über eine lokale Variable zugreifen möchten. Die Regel ist, dass es vorher definitiv zugewiesen werden sollte.

Um einen Feldzugriffsausdruck auszuwerten foo.x, muss zuerst der primaryTeil von it ( foo) ausgewertet werden. fooDies bedeutet, dass der Zugriff auf erfolgt, was zu einem Fehler bei der Kompilierung führt.

Für jeden Zugriff auf eine lokale Variable oder ein leeres Endfeld x muss x vor dem Zugriff definitiv zugewiesen werden, da sonst ein Fehler bei der Kompilierung auftritt.

Andrew Tobilko
quelle
14

Es ist sinnvoll, die Regeln so einfach wie möglich zu halten, und „Verwenden Sie keine Variable, die möglicherweise nicht initialisiert wurde“ ist so einfach wie es nur geht.

Genauer gesagt gibt es eine etablierte Methode zum Aufrufen statischer Methoden - verwenden Sie immer den Klassennamen, keine Variable.

System.out.println(Foo.x);

Die Variable "foo" ist unerwünschter Overhead, der entfernt werden sollte, und die Compilerfehler und Warnungen könnten als hilfreich angesehen werden, um dies zu erreichen.

Racraman
quelle
3

Andere Antworten erklären perfekt den Mechanismus hinter dem, was passiert. Vielleicht wollten Sie auch die Gründe für Javas Spezifikation. Da ich kein Java-Experte bin, kann ich Ihnen nicht die ursprünglichen Gründe nennen, aber lassen Sie mich darauf hinweisen:

  • Jeder Code hat entweder eine Bedeutung oder löst einen Kompilierungsfehler aus.
  • (Für die Statik Foo.xist es natürlich , weil eine Instanz unnötig ist.)
  • Was sollen wir nun tun foo.x(Zugriff über Instanzvariable)?
    • Es könnte sich um einen Kompilierungsfehler handeln, wie in C # oder
    • Es hat eine Bedeutung. Da dies Foo.xbereits "einfacher Zugriff x" bedeutet, ist es vernünftig, dass der Ausdruck foo.x eine andere Bedeutung hat. Das heißt, jeder Teil des Ausdrucks ist gültig und zugänglich x.

Hoffen wir, dass jemand, der sich auskennt, den wahren Grund erkennen kann. :-)

Pablo H.
quelle