Sollte ich in Java "final" für Parameter und Locals verwenden, auch wenn ich das nicht muss?

105

In Java können Variablen (Felder / Gebietsschemas / Parameter) als markiert werden final, um eine Neuzuweisung zu verhindern. Ich finde es sehr nützlich bei Feldern, da es mir hilft, schnell zu erkennen, ob einige Attribute - oder eine ganze Klasse - unveränderlich sein sollen.

Andererseits finde ich es bei Einheimischen und Parametern weniger nützlich, und normalerweise vermeide ich es, sie als finalsolche zu kennzeichnen, auch wenn sie niemals neu zugewiesen werden (mit der offensichtlichen Ausnahme, wenn sie in einer inneren Klasse verwendet werden müssen). . In letzter Zeit bin ich jedoch auf Code gestoßen, der, wann immer möglich, final verwendet, was meiner Meinung nach technisch mehr Informationen liefert.

Ich bin mir meines Programmierstils nicht mehr sicher und frage mich, welche anderen Vor- und Nachteile es gibt final, wenn ich mich irgendwo bewerbe, was der gängigste Industriestil ist und warum.

Eiche
quelle
@Amir-Fragen im Coding-Stil scheinen hier besser zu sein als auf SO, und ich konnte diesbezüglich keine Richtlinie in den FAQ oder auf den Metas dieser Site finden. Kannst du mich bitte leiten?
Oak
1
@ Oak Es heißt: "Spezifische Programmierprobleme, Software-Algorithmen, Codierung , fragen Sie nach Stack Overflow"
Amir Rezaei
7
@Amir ich bin anderer Meinung, ich verstehe nicht, wie das ein Codierungsproblem ist. Auf jeden Fall gehört diese Debatte nicht hierher, deshalb habe ich ein Meta-Thema zu diesem Thema eröffnet .
Oak
3
@amir dies ist ein Thema für diese Seite, da es sich um eine subjektive Frage zur Programmierung handelt - siehe / faq
Jeff Atwood
1
Zu lokalen Variablen: Ältere Java-Compiler haben darauf geachtet, ob Sie eine lokale deklariert haben finaloder nicht, und entsprechend optimiert. Moderne Compiler sind schlau genug, es selbst herauszufinden. Zumindest auf lokale Variablen, finalist ausschließlich zum Nutzen der menschlichen Leser. Wenn Ihre Routinen nicht zu kompliziert sind, sollten die meisten menschlichen Leser es auch selbst herausfinden können.
Solomon Slow

Antworten:

67

Ich benutze finalden gleichen Weg wie du. Für mich sind lokale Variablen und Methodenparameter überflüssig und es werden keine nützlichen Zusatzinformationen übermittelt.

Eine wichtige Sache ist, dass ich mich bemühe , meine Methoden kurz und sauber zu halten und jede einzelne Aufgabe zu erledigen. Daher haben meine lokalen Variablen und Parameter einen sehr begrenzten Gültigkeitsbereich und werden nur für einen einzigen Zweck verwendet. Dies minimiert die Wahrscheinlichkeit einer versehentlichen Neuzuweisung.

Darüber hinaus kann, wie Sie sicherlich wissen, finalnicht garantiert werden, dass Sie den Wert / Status einer (nicht primitiven) Variablen nicht ändern können. Nur, dass Sie den Verweis nach der Initialisierung nicht mehr diesem Objekt zuweisen können. Mit anderen Worten, es funktioniert nahtlos nur mit Variablen primitiven oder unveränderlichen Typs. Erwägen

final String s = "forever";
final int i = 1;
final Map<String, Integer> m = new HashMap<String, Integer>();

s = "never"; // compilation error!
i++; // compilation error!
m.put(s, i); // fine

Dies bedeutet, dass es in vielen Fällen immer noch nicht einfacher ist zu verstehen, was im Code passiert, und dass ein Missverständnis dazu führen kann, dass subtile Fehler auftreten, die schwer zu erkennen sind.

Péter Török
quelle
1
In Bezug auf die Bearbeitung - Ich kenne die Semantik von final, danke :), aber ein guter Punkt zu kurzen und sauberen Methoden - Ich denke, wenn die Methode kurz genug ist, dass es offensichtlich ist, dass eine Variable nicht neu zugewiesen wird, gibt es eine Noch weniger Anlass, über das finalSchlagwort nachzudenken.
Oak
Wäre es nicht großartig, wenn wir endgültige Parameter und lokale Variablen und dennoch eine kurze und saubere Syntax hätten? programmers.stackexchange.com/questions/199783/…
oberlies
7
"final garantiert nicht, dass Sie den Wert / Status einer (nicht primitiven) Variablen nicht ändern können. Nur, dass Sie den Verweis auf dieses Objekt nach der Initialisierung nicht neu zuweisen können." Nicht primitiv = Referenz (die einzigen Typen in Java sind primitive Typen und Referenztypen). Der Wert einer Variablen des Referenztyps ist eine Referenz. Daher können Sie die Referenz nicht neu zuweisen = Sie können den Wert nicht ändern.
user102008
2
+1 für die Referenz- / Statusfalle. Beachten Sie jedoch, dass das Markieren der Variablen final erforderlich sein kann, um die neuen funktionalen Aspekte von Java8 abzuschließen
Newtopian,
Ihr Vergleich ist voreingenommen, wie @ user102008 betont. Die Zuweisung der Variablen ist nicht mit der Aktualisierung der Werte
identisch
64

Ihr Java-Programmierstil und Ihre Gedanken sind in Ordnung - Sie müssen dort nicht an sich selbst zweifeln.

Andererseits finde ich es bei Einheimischen und Parametern weniger nützlich, und normalerweise vermeide ich es, sie als endgültig zu kennzeichnen, selbst wenn sie niemals neu zugewiesen werden (mit der offensichtlichen Ausnahme, wenn sie in einer inneren Klasse verwendet werden müssen) ).

Genau aus diesem Grund sollten Sie das finalSchlüsselwort verwenden. Sie geben an, dass SIE wissen, dass es niemals neu zugewiesen wird, aber niemand anderes weiß das. Durch die finalsofortige Verwendung wird Ihr Code ein bisschen eindeutiger.

Jonathan Khoo
quelle
8
Wenn Ihre Methode klar ist und nur eines tut, wird der Leser es auch wissen. Das letzte Wort macht den Code nur unleserlich. Und wenn es unleserlich ist, ist es viel mehrdeutiger
eddieferetro
4
@eddieferetro nicht einverstanden. Das Schlüsselwort finalgibt die Absicht an, wodurch der Code besser lesbar wird. Sobald Sie sich mit Code aus der realen Welt befassen müssen, werden Sie feststellen, dass er selten makellos und klar ist. Wenn finalSie überall s hinzufügen , können Sie Fehler besser erkennen und älteren Code besser verstehen.
Andres F.
4
Ist die "Absicht", dass sich die Variable niemals ändert und dass Ihr Code sich auf diese Tatsache stützt ? Oder ist die Absicht "es passiert einfach so, dass sich diese Variable nie ändert, also markiere ich sie als endgültig"? Letzteres ist nutzlos und möglicherweise schädlichen Lärm. Und da Sie zu befürworten scheinen, alle Einheimischen zu markieren , tun Sie dies später. Groß -1.
user949300
2
finalgibt die Absicht an, die normalerweise in einem Stück Code gut ist, aber es geht um den Preis des Hinzufügens von visueller Unordnung. Wie die meisten Dinge in der Programmierung gibt es hier einen Kompromiss: Drücken Sie die Tatsache aus, dass Ihre Variable nur einmal verwendet wird und die zusätzliche Ausführlichkeit wert ist?
christopheml
30

Ein Vorteil der Verwendung von final/, constwo immer dies möglich ist, besteht darin, dass die mentale Belastung für den Leser Ihres Codes verringert wird.

Er kann sicher sein, dass der Wert / die Referenz später nie mehr geändert wird. Er muss also nicht auf Änderungen achten, um die Berechnung zu verstehen.

Ich habe diesbezüglich meine Meinung geändert, nachdem ich rein funktionale Programmiersprachen gelernt habe. Junge, was für eine Erleichterung, wenn Sie darauf vertrauen können, dass eine "Variable" immer ihren Anfangswert behält.

LennyProgrammierer
quelle
16
Nun, in Java kann finalnicht garantiert werden, dass Sie den Wert / Status einer (nicht primitiven) Variablen nicht ändern können. Nur, dass Sie den Verweis nach der Initialisierung nicht mehr diesem Objekt zuweisen können.
Péter Török
9
Ich weiß, deshalb habe ich zwischen Wert und Referenz unterschieden. Das Konzept ist am nützlichsten im Zusammenhang mit unveränderlichen Datenstrukturen und / oder reinen Funktionen.
LennyProgrammers
7
@ PéterTörök Es ist sofort hilfreich bei primitiven Typen und etwas hilfreich bei Verweisen auf veränderbare Objekte (zumindest wissen Sie, dass es sich immer um dasselbe Objekt handelt!). Es ist immens hilfreich, wenn es um Code geht, der von Grund auf unveränderlich ist.
Andres F.
18

Ich betrachte finalin Methodenparametern und lokalen Variablen Code-Rauschen. Java-Methodendeklarationen können (insbesondere bei Generika) sehr lang sein - sie müssen nicht mehr erstellt werden.

Wenn Unit - Tests richtig geschrieben werden, um Parameter zuweisen , die „schädlichen“ werden bei Ihnen abgeholt ist, so sollte es nie wirklich ein Problem sein. Die visuelle Klarheit ist wichtiger als die Vermeidung eines möglichen Fehlers, der nicht erkannt wird, da Ihre Komponententests eine unzureichende Abdeckung aufweisen.

Tools wie FindBugs und CheckStyle, die so konfiguriert werden können, dass sie den Build unterbrechen, wenn Parameter oder lokale Variablen zugewiesen werden, wenn Sie sich intensiv mit solchen Dingen befassen.

Natürlich, wenn Sie benötigen sie endgültig zum Beispiel zu machen , weil Sie den Wert in einer anonymen Klasse verwenden, dann kein Problem - das ist die einfachste sauberste Lösung.

Abgesehen von dem offensichtlichen Effekt, zusätzliche Schlüsselwörter zu Ihren Parametern hinzuzufügen und diese dadurch zu tarnen, kann das Hinzufügen von final zu Methodenparametern häufig dazu führen, dass der Code im Methodenkörper weniger lesbar wird, was den Code verschlimmert - um "guter" Code zu sein muss so lesbar und einfach wie möglich sein. Nehmen wir als Beispiel an, ich hätte eine Methode, bei der die Groß- und Kleinschreibung nicht berücksichtigt werden muss.

Ohne final:

public void doSomething(String input) {
    input = input.toLowerCase();
    // do a few things with input
}

Einfach. Reinigen. Jeder weiß, was los ist.

Jetzt mit 'final', Option 1:

public void doSomething(final String input) {
    final String lowercaseInput = input.toLowerCase();
    // do a few things with lowercaseInput
}

Während das finalHinzufügen von Code durch die Parameter verhindert wird, dass der Codierer glaubt, er arbeite mit dem ursprünglichen Wert, besteht das gleiche Risiko, dass Code, der weiter unten liegt, inputanstelle dessen verwendet wird lowercaseInput, was er nicht sollte und gegen das er nicht geschützt werden kann, weil Sie dies können. ' t take it out of scope (oder auch zuweisen null, inputwenn das überhaupt helfen würde).

Mit 'final', Option 2:

public void doSomething(final String input) {
    // do a few things with input.toLowerCase()
}

Jetzt haben wir nur noch mehr Code-Rauschen erzeugt und den Performance-Hit eingeführt, toLowerCase()n-mal aufgerufen zu werden .

Mit 'final', Option 3:

public void doSomething(final String input) {
    doSomethingPrivate(input.toLowerCase());
}

/** @throws IllegalArgumentException if input not all lower case */
private void doSomethingPrivate(final String input) {
    if (!input.equals(input.toLowerCase())) {
        throw new IllegalArgumentException("input not lowercase");
    }
    // do a few things with input
}

Sprechen Sie über Code-Rauschen. Dies ist ein Zugunglück. Wir haben eine neue Methode, einen erforderlichen Ausnahmeblock, da anderer Code ihn möglicherweise falsch aufruft. Weitere Unit-Tests, um die Ausnahme abzudecken. Alles, um eine einfache und meiner Meinung nach vorzuziehende und harmlose Linie zu vermeiden.

Es gibt auch das Problem, dass Methoden nicht so lang sein sollten, dass Sie sie nicht einfach visuell erfassen können und auf einen Blick wissen, dass eine Zuweisung zu Parametern stattgefunden hat.

Ich halte es für eine gute Praxis / einen guten Stil, wenn Sie einem Parameter einen Wert zuweisen, den Sie zu Beginn der Methode ausführen, vorzugsweise in der ersten Zeile oder direkt nach der Überprüfung der grundlegenden Eingaben, wodurch die gesamte Methode ersetzt wird Methode. Die Leser wissen, dass jede Zuordnung offensichtlich (in der Nähe der Signaturdeklaration) und an einer konsistenten Stelle erfolgen muss, wodurch das Problem, das durch das Hinzufügen von final vermieden werden soll, erheblich verringert wird. Eigentlich ordne ich selten Parameter zu, aber wenn ich das tue, tue ich es immer am Anfang einer Methode.


Beachten Sie auch, dass Sie finalnicht wirklich so geschützt sind, wie es auf den ersten Blick scheint:

public void foo(final Date date) {
    date.setTime(0); 
    // code that uses date
}

final Schützt Sie nur dann vollständig, wenn der Parametertyp primitiv oder unveränderlich ist.

Bohemien
quelle
Bietet im letzten Fall finaldie teilweise Garantie, dass es sich um dieselbe DateInstanz handelt. Einige Garantien sind besser als nichts (wenn überhaupt, streiten Sie sich von Grund auf für unveränderliche Klassen!). In der Praxis sollte sich vieles, was Sie sagen, auf vorhandene unveränderliche Standardsprachen auswirken, was jedoch nicht der Fall ist. Dies bedeutet, dass dies kein Problem darstellt.
Andres F.
+1 für den Hinweis auf das Risiko, dass der Code weiter unten möglicherweise Eingaben verwendet. Trotzdem würde ich es vorziehen, wenn meine Parameter so sind final, dass sie für Fälle wie oben nicht endgültig sind. Es lohnt sich einfach nicht, die Signatur mit einem Schlüsselwort zu spammen.
Maaartinus
7

Ich lasse eclipse finalvor jede lokale Variable stellen, da ich der Meinung bin, dass dies das Programm leichter lesbar macht. Ich mache es nicht mit Parametern, da ich die Parameterliste so kurz wie möglich halten möchte, idealerweise sollte sie in eine Zeile passen.

maaartinus
quelle
2
Bei Parametern kann der Compiler auch eine Warnung oder einen Fehler ausgeben, wenn ein Parameter zugewiesen ist.
oberlies
3
Im Gegenteil, ich finde es schwieriger zu lesen, da es nur den Code durcheinander bringt.
Steve Kuo
3
@ Steve Kuo: Es ermöglicht mir nur, schnell alle Variablen zu finden. Kein großer Gewinn, aber zusammen mit der Vermeidung von versehentlichen Zuweisungen sind es die 6 Zeichen wert. Ich wäre viel glücklicher, wenn es so etwas wie das varMarkieren von nicht endgültigen Variablen gäbe . YMMV.
Maaartinus
1
@maaartinus Einverstanden über var! Leider ist dies nicht die Standardeinstellung in Java und jetzt ist es zu spät, um die Sprache zu ändern. Daher bin ich bereit, die kleinen Unannehmlichkeiten des Schreibens in Kauf zu nehmen final:)
Andres F.
1
@maaartinus Einverstanden. Ich finde, dass ungefähr 80-90% meiner (lokalen) Variablen nach der Initialisierung nicht geändert werden müssen. Daher haben 80-90% von ihnen das finalSchlüsselwort vorangestellt, während nur 10-20% ein var...
JimmyB