it-swarm.com.de

Warum werden lokale Variablen nicht in Java initialisiert?

Gab es einen Grund, warum die Entwickler von Java der Meinung waren, dass lokale Variablen keinen Standardwert erhalten sollten? Im Ernst, wenn Instanzvariablen ein Standardwert zugewiesen werden kann, warum können wir dann nicht dasselbe für lokale Variablen tun?

Und es führt auch zu Problemen, wie in dieser Kommentar zu einem Blogbeitrag :

Diese Regel ist sehr frustrierend, wenn versucht wird, eine Ressource in einem finally-Block zu schließen. Wenn ich die Ressource in einem Versuch instanziiere, aber versuche, sie innerhalb von final zu schließen, erhalte ich diesen Fehler. Wenn ich die Instantiierung außerhalb des try versetze, erhalte ich einen weiteren Fehler, der besagt, dass es sich um einen try handeln muss.

Sehr frustrierend.

91

Lokale Variablen werden hauptsächlich deklariert, um Berechnungen durchzuführen. Es ist also die Entscheidung des Programmierers, den Wert der Variablen festzulegen und sollte keinen Standardwert annehmen. Wenn der Programmierer aus Versehen eine lokale Variable nicht initialisiert hat und einen Standardwert verwendet, kann die Ausgabe einen unerwarteten Wert annehmen. Bei lokalen Variablen fordert der Compiler den Programmierer daher auf, mit einem bestimmten Wert zu initialisieren, bevor er auf die Variable zugreift, um die Verwendung nicht definierter Werte zu vermeiden.

55
Warrior

Das "Problem", auf das Sie verlinken scheint diese Situation zu beschreiben:

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

Die Beschwerde des Kommentators ist, dass der Compiler die Zeile im Abschnitt finally blockiert und behauptet, dass so möglicherweise nicht initialisiert ist. Der Kommentar erwähnt dann eine andere Art, den Code zu schreiben, wahrscheinlich etwa so:

// Do some work here ...
SomeObject so = new SomeObject();
try {
  so.DoUsefulThings();
} finally {
  so.CleanUp();
}

Der Kommentator ist mit dieser Lösung unzufrieden, da der Compiler dann sagt, dass der Code "innerhalb eines Versuchs sein muss". Ich denke, das bedeutet, dass ein Teil des Codes eine Ausnahme auslöst, die nicht mehr behandelt wird. Ich bin mir nicht sicher. Keine der beiden Versionen meines Codes behandelt Ausnahmen, daher sollte alles, was in der ersten Version mit Ausnahmen zu tun hat, in der zweiten Version genauso funktionieren.

Wie auch immer, diese zweite Codeversion ist die richtige Art, sie zu schreiben. In der ersten Version war die Fehlermeldung des Compilers korrekt. Die Variable so ist möglicherweise nicht initialisiert. Insbesondere wenn der Konstruktor SomeObject fehlschlägt, wird so nicht initialisiert, und daher ist es ein Fehler, zu versuchen, so.CleanUp aufzurufen. Geben Sie immer den Abschnitt try ein, nachdem Sie die Ressource erworben haben, die im Abschnitt finally finalisiert wurde.

Der Block try-finally nach der so-Initialisierung dient nur zum Schutz des SomeObject Zum Beispiel, um sicherzustellen, dass es aufgeräumt wird, egal was sonst passiert. Wenn es andere Dinge gibt, die ausgeführt werden müssen, die sich jedoch nicht darauf beziehen, ob der Instanz SomeObject eine Eigenschaft zugewiesen wurde, sollten sie dies tun gehe in einen anderen try-finally Block, wahrscheinlich einen, der den von mir gezeigten umschließt.

Das Erfordernis der manuellen Zuweisung von Variablen vor der Verwendung führt nicht zu echten Problemen. Es führt nur zu geringfügigen Problemen, aber Ihr Code wird besser dafür sein. Sie werden Variablen mit eingeschränkterem Bereich und try-finally -Blöcken haben, die nicht versuchen, zu viel zu schützen.

Wenn lokale Variablen Standardwerte hätten, wäre so im ersten Beispiel null gewesen. Das hätte nichts wirklich gelöst. Anstatt einen Kompilierungsfehler im Block finally zu erhalten, lauert dort ein NullPointerException, das möglicherweise verbirgt Jede andere Ausnahme kann im Abschnitt "Arbeiten Sie hier" des Codes auftreten. (Oder werden Ausnahmen in finally Abschnitten automatisch mit der vorherigen Ausnahme verkettet? Ich erinnere mich nicht. Trotzdem hätten Sie eine zusätzliche Ausnahme in Bezug auf die reale Ausnahme.)

22
Rob Kennedy

Im folgenden Beispiel wurde möglicherweise eine Ausnahme innerhalb der SomeObject-Konstruktion ausgelöst. In diesem Fall wäre die Variable 'so' null und der Aufruf von CleanUp löst eine NullPointerException aus

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

Was ich dazu neige, ist folgendes:

SomeObject so = null;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  if (so != null) {
     so.CleanUp(); // safe
  }
}
12
Electric Monk

Beachten Sie, dass die finalen Instanz-/Member-Variablen nicht standardmäßig initialisiert werden. Denn diese sind endgültig und können danach nicht mehr im Programm geändert werden. Aus diesem Grund gibt Java keinen Standardwert für sie an und zwingt den Programmierer, ihn zu initialisieren. 

Nicht-endgültige Member-Variablen können jedoch später geändert werden. Der Compiler lässt sie also nicht uninitialisiert bleiben, weil diese später geändert werden können. In Bezug auf lokale Variablen ist der Umfang der lokalen Variablen viel enger. Compiler weiß, wann es gewöhnungsbedürftig ist. Daher ist es sinnvoll, den Programmierer zu veranlassen, die Variable zu initialisieren.

10
Adeel Ansari

Die eigentliche Antwort auf Ihre Frage ist, dass Methodenvariablen instanziiert werden, indem Sie einfach eine Nummer zum Stack-Pointer hinzufügen. Sie auf Null zu setzen, wäre ein zusätzlicher Schritt. Bei Klassenvariablen werden sie auf dem Heap in den initialisierten Speicher gestellt.

Warum machen Sie nicht den zusätzlichen Schritt? Machen Sie einen Schritt zurück - niemand hat erwähnt, dass die "Warnung" in diesem Fall eine sehr gute Sache ist.

Sie sollten Ihre Variable niemals beim ersten Durchgang (beim ersten Codieren) mit Null oder Null initialisieren. Weisen Sie es entweder dem tatsächlichen Wert zu, oder weisen Sie es überhaupt nicht zu, denn wenn Sie dies nicht tun, kann Java Ihnen sagen, wann Sie wirklich einen Fehler machen. Nehmen Sie die Antwort von Electric Monk als gutes Beispiel. Im ersten Fall ist es erstaunlich nützlich, dass es Ihnen mitteilt, dass das try () fehlschlägt, weil der Konstruktor von SomeObject eine Ausnahme ausgelöst hat, dass Sie am Ende eine NPE erhalten. Wenn der Konstruktor keine Ausnahme auslösen kann, sollte dies nicht im Versuch sein.

Diese Warnung ist ein großartiger Multi-Path-Programmierer, der mich vor dummen Dingen bewahrt hat, da er jeden Pfad überprüft und sicherstellt, dass Sie die Variable in einem Pfad verwenden mussten, den Sie in jedem Pfad initialisieren mussten . Ich initialisiere Variablen jetzt nie explizit, bis ich feststelle, dass dies richtig ist.

Ist es nicht besser, explizit "int size = 0" anstelle von "int size" zu sagen und den nächsten Programmierer dazu zu bringen, herauszufinden, dass Sie beabsichtigen, es sei Null?

Auf der anderen Seite kann ich keinen einzigen gültigen Grund dafür finden, dass der Compiler alle nicht initialisierten Variablen auf 0 initialisiert.

9
Bill K

(Es mag seltsam erscheinen, eine neue Antwort so lange nach der Frage zu posten, aber ein duplicate erschien.)

Für mich kommt das reason auf folgendes: Der Zweck von lokalen Variablen unterscheidet sich von dem der Instanzvariablen. Lokale Variablen dienen dazu, als Teil einer Berechnung verwendet zu werden. Instanzvariablen enthalten den Zustand. Wenn Sie eine lokale Variable verwenden, ohne dieser einen Wert zuzuweisen, ist dies höchstwahrscheinlich ein logischer Fehler.

Trotzdem konnte ich völlig hinterherhinken, dass Instanzvariablen immer explizit initialisiert werden mussten. Der Fehler würde bei jedem Konstruktor auftreten, bei dem das Ergebnis eine nicht initialisierte Instanzvariable zulässt (z. B. bei der Deklaration nicht initialisiert und nicht im Konstruktor). Aber das ist nicht die Entscheidung von Gosling et. al., Anfang der 90er Jahre aufgenommen, hier sind wir also. (Und ich sage nicht, dass sie den falschen Anruf gemacht haben.)

Ich könnte jedoch not hinter den Standardvariablen der lokalen Variablen stehen. Ja, wir sollten uns nicht auf Compiler verlassen, um unsere Logik noch einmal zu überprüfen, und eine tut es nicht, aber es ist immer noch praktisch, wenn der Compiler eine Ahnung hat. :-)

4
T.J. Crowder

Ich denke, der Hauptzweck war, die Ähnlichkeit mit C/C++ aufrechtzuerhalten. Der Compiler erkennt und warnt Sie jedoch vor der Verwendung nicht initialisierter Variablen, wodurch das Problem auf ein Minimum reduziert wird. Aus Performance-Sicht ist es etwas schneller, wenn Sie nicht initialisierte Variablen deklarieren lassen, da der Compiler keine Zuweisungsanweisung schreiben muss, selbst wenn Sie den Wert der Variablen in der nächsten Anweisung überschreiben.

3
Mehrdad Afshari

Es ist effizienter, Variablen nicht zu initialisieren, und bei lokalen Variablen ist dies sicher möglich, da die Initialisierung vom Compiler nachverfolgt werden kann.

In den Fällen, in denen Sie eine Variable initialisieren müssen, können Sie dies immer selbst tun. Dies ist also kein Problem.

2
starblue

Die Idee hinter lokalen Variablen ist, dass sie nur in dem begrenzten Umfang existieren, für den sie benötigt werden. Daher sollte es wenig Anlass geben, Ungewissheit hinsichtlich des Werts oder zumindest des Ursprungs dieses Werts zu haben. Ich könnte mir viele Fehler vorstellen, die sich aus einem Standardwert für lokale Variablen ergeben.

Betrachten Sie zum Beispiel den folgenden einfachen Code ... (N.B. lassen Sie uns zu Demonstrationszwecken davon ausgehen, dass lokalen Variablen wie angegeben ein Standardwert zugewiesen wird, wenn sie nicht explizit initialisiert sind.

System.out.println("Enter grade");
int grade = new Scanner(System.in).nextInt(); //I won't bother with exception handling here, to cut down on lines.
char letterGrade; //let us assume the default value for a char is '\0'
if (grade >= 90)
    letterGrade = 'A';
else if (grade >= 80)
    letterGrade = 'B';
else if (grade >= 70)
    letterGrade = 'C';
else if (grade >= 60)
    letterGrade = 'D';
else
    letterGrade = 'F';
System.out.println("Your grade is " + letterGrade);

Wenn alles gesagt und getan ist, vorausgesetzt, der Compiler hat letterGrade einen Standardwert von "\ 0" zugewiesen, würde dieser Code, so wie er geschrieben wurde, ordnungsgemäß funktionieren. Was aber, wenn wir die else-Anweisung vergessen haben?

Ein Testlauf unseres Codes kann folgendes zur Folge haben

Enter grade
43
Your grade is

Dieses Ergebnis war zwar nicht zu erwarten, war aber sicherlich nicht die Absicht des Programmierers. Wahrscheinlich würde der Standardwert in einer großen Mehrheit der Fälle (oder zumindest einer signifikanten Anzahl davon) nicht den gewünschten gewünschten -Wert sein. In der großen Mehrheit der Fälle würde der Standardwert zu Fehlern führen . Es ist sinnvoller, den Codierer zu zwingen, einer lokalen Variablen vor der Verwendung einen Anfangswert zuzuweisen, da das Debugging-Problem, das durch das Vergessen des = 1 in for(int i = 1; i < 10; i++) verursacht wird, die Leichtigkeit überwiegt, wenn er den = 0 nicht in for(int i; i < 10; i++) aufnehmen muss.

Es ist wahr, dass try-catch-finally-Blöcke etwas unordentlich werden könnten (aber es ist nicht wirklich ein catch-22, wie das Zitat zu suggerieren scheint), wenn zum Beispiel ein Objekt in seinem Konstruktor eine geprüfte Ausnahme auslöst, jedoch für einen Grund oder ein anderer, etwas muss mit diesem Objekt am Ende des Blocks in endlich gemacht werden. Ein perfektes Beispiel dafür ist der Umgang mit Ressourcen, die geschlossen werden müssen.

Eine Möglichkeit, dies in der Vergangenheit zu bewältigen, könnte so sein ...

Scanner s = null; //declared and initialized to null outside the block. This gives us the needed scope, and an initial value.
try {
    s = new Scanner(new FileInputStream(new File("filename.txt")));
    int someInt = s.nextInt();
} catch (InputMismatchException e) {
    System.out.println("Some error message");
} catch (IOException e) {
    System.out.println("different error message"); 
} finally {
    if (s != null) //in case exception during initialization prevents assignment of new non-null value to s.
        s.close();
}

Ab Java 7 ist dieser endgültige Block jedoch nicht mehr erforderlich, wenn Try-With-Ressourcen verwendet werden.

try (Scanner s = new Scanner(new FileInputStream(new File("filename.txt")))) {
...
...
} catch(IOException e) {
    System.out.println("different error message");
}

Das heißt (wie der Name schon sagt), das funktioniert nur mit Ressourcen.

Und obwohl das erste Beispiel ein bisschen glücklich ist, spricht dies vielleicht mehr für die Art und Weise, wie try-catch-finally oder diese Klassen implementiert werden, als über lokale Variablen und wie sie implementiert werden.

Es stimmt, dass Felder mit einem Standardwert initialisiert werden, dies ist jedoch ein bisschen anders. Wenn Sie beispielsweise int[] arr = new int[10]; sagen, sobald Sie dieses Array initialisiert haben, ist das Objekt an einem bestimmten Ort im Speicher vorhanden. Nehmen wir für einen Moment an, dass es keine Standardwerte gibt, stattdessen ist der Anfangswert eine Reihe von 1er und 0er, die sich gerade an diesem Speicherplatz befinden. Dies kann in einigen Fällen zu nicht deterministischem Verhalten führen.

Angenommen, wir haben ...

int[] arr = new int[10];
if(arr[0] == 0)
    System.out.println("Same.");
else
    System.out.println("Not same.");

Es ist durchaus möglich, dass Same. in einem Durchlauf und Not same. in einem anderen Lauf angezeigt wird. Das Problem kann noch schwerwiegender werden, wenn Sie Referenzvariablen sprechen.

String[] s = new String[5];

Definitionsgemäß sollte jedes Element von s auf einen String zeigen (oder null sein). Wenn der Anfangswert jedoch eine Reihe von 0 und 1 ist, die an diesem Speicherplatz vorkommen, gibt es nicht nur keine Garantie, dass Sie jedes Mal die gleichen Ergebnisse erhalten, sondern es gibt auch keine Garantie dafür, dass das Objekt [0] zeigt to (vorausgesetzt, es deutet auf etwas Sinnvolles hin) sogar ist ein String (vielleicht ist es ein Kaninchen, : p)! Diese mangelnde Sorge um den Typ würde so ziemlich alles, was Java zu Java macht, ins Gesicht sehen. Während Standardwerte für lokale Variablen bestenfalls als optional angesehen werden können, liegt die Verwendung von Standardwerten für Instanzvariablen einem Notwendigkeit näher.

1
kumquatfelafel

Eclipse warnt Sie sogar vor nicht initialisierten Variablen, so dass es ohnehin offensichtlich wird. Ich persönlich finde es gut, dass dies das Standardverhalten ist. Andernfalls verwendet Ihre Anwendung möglicherweise unerwartete Werte. Statt dass der Compiler einen Fehler ausgibt, wird er nichts tun (aber möglicherweise eine Warnung geben) Ihr Kopf darüber, warum bestimmte Dinge sich nicht so verhalten, wie sie sollten.

0
Kezzer

Die lokalen Variablen werden in einem Stapel gespeichert, aber Instanzvariablen werden auf dem Heap gespeichert. Daher besteht die Möglichkeit, dass ein vorheriger Wert im Stapel anstelle eines Standardwerts gelesen wird, wie dies im Heap der Fall ist. Aus diesem Grund erlaubt es der jvm nicht, eine lokale Variable zu verwenden, ohne sie zu initialisieren.

0

Instanzvariable hat Standardwerte, die lokalen Variablen konnten jedoch keine Standardwerte haben. Da lokale Variablen im Wesentlichen in Methoden/Verhalten enthalten sind, besteht das Hauptziel darin, einige Operationen oder Berechnungen durchzuführen. Daher ist es nicht ratsam, Standardwerte für lokale Variablen festzulegen. Ansonsten ist es sehr schwer und zeitaufwendig, die Gründe für unerwartete Antworten zu überprüfen.

0
Xiaogang