459 Stimmen

Warum sollte ich das Schlüsselwort "final" für einen Methodenparameter in Java verwenden?

Ich kann nicht verstehen, wo die final Schlüsselwort ist wirklich praktisch, wenn es für Methodenparameter verwendet wird.

Wenn wir die Verwendung anonymer Klassen, die Lesbarkeit und die Absichtserklärung ausklammern, scheint es mir fast wertlos zu sein.

Zu erzwingen, dass einige Daten konstant bleiben, ist nicht so einfach, wie es scheint.

  • Handelt es sich bei dem Parameter um ein Primitivum, hat dies keine Auswirkungen, da der Parameter als Wert an die Methode übergeben wird und seine Änderung keine Auswirkungen außerhalb des Anwendungsbereichs hat.

  • Wenn wir einen Parameter per Verweis übergeben, dann ist der Verweis selbst eine lokale Variable, und wenn der Verweis innerhalb der Methode geändert wird, hat das keine Auswirkungen außerhalb des Methodenbereichs.

Betrachten Sie das folgende einfache Testbeispiel. Dieser Test ist erfolgreich, obwohl die Methode den Wert des ihr übergebenen Verweises geändert hat, er hat also keine Auswirkungen.

public void testNullify() {
    Collection<Integer> c  = new ArrayList<Integer>();      
    nullify(c);
    assertNotNull(c);       
    final Collection<Integer> c1 = c;
    assertTrue(c1.equals(c));
    change(c);
    assertTrue(c1.equals(c));
}

private void change(Collection<Integer> c) {
    c = new ArrayList<Integer>();
}

public void nullify(Collection<?> t) {
    t = null;
}

348voto

Basil Bourque Punkte 256611

Die Neuzuweisung einer Variable stoppen

Obwohl diese Antworten intellektuell interessant sind, habe ich die kurze, einfache Antwort nicht gelesen:

Verwenden Sie das Schlüsselwort endgültig wenn Sie wollen, dass der Compiler eine verhindern soll, dass eine Variable einem anderen Objekt erneut zugewiesen wird.

Unabhängig davon, ob es sich bei der Variablen um eine statische Variable, eine Member-Variable, eine lokale Variable oder eine Argument/Parameter-Variable handelt, ist die Wirkung völlig gleich.

Beispiel

Schauen wir uns die Wirkung in Aktion an.

Betrachten wir diese einfache Methode, bei der die beiden Variablen ( arg y x ) können beide anderen Objekten zugewiesen werden.

// Example use of this method: 
//   this.doSomething( "tiger" );
void doSomething( String arg ) {
  String x = arg;   // Both variables now point to the same String object.
  x = "elephant";   // This variable now points to a different String object.
  arg = "giraffe";  // Ditto. Now neither variable points to the original passed String.
}

Markieren Sie die lokale Variable als endgültig . Dies führt zu einem Compilerfehler.

void doSomething( String arg ) {
  final String x = arg;  // Mark variable as 'final'.
  x = "elephant";  // Compiler error: The final local variable x cannot be assigned. 
  arg = "giraffe";  
}

Stattdessen markieren wir die Parametervariable als endgültig . Auch dies führt zu einem Compilerfehler.

void doSomething( final String arg ) {  // Mark argument as 'final'.
  String x = arg;   
  x = "elephant"; 
  arg = "giraffe";  // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
}

Die Moral von der Geschicht':

Wenn Sie sicherstellen wollen, dass eine Variable immer auf dasselbe Objekt zeigt, markieren Sie die Variable endgültig .

Niemals Argumente neu zuordnen

Als gute Programmierpraxis (in jeder Sprache), sollten Sie niemals eine Parameter-/Argumentvariable einem anderen Objekt als dem von der aufrufenden Methode übergebenen Objekt neu zuweisen. In den obigen Beispielen sollte man niemals die Zeile arg = . Da Menschen Fehler machen und Programmierer auch nur Menschen sind, sollten wir den Compiler bitten, uns zu helfen. Kennzeichnen Sie jede Parameter-/Argumentvariable als "endgültig", damit der Compiler derartige Neuzuweisungen finden und kennzeichnen kann.

Im Rückblick

Wie in anderen Antworten erwähnt In Anbetracht des ursprünglichen Ziels von Java, Programmierern zu helfen, dumme Fehler zu vermeiden, wie z.B. das Lesen über das Ende eines Arrays hinaus, hätte Java so entworfen werden sollen, dass alle Parameter/Argumentvariablen automatisch als 'final' erzwungen werden. Mit anderen Worten, Argumente sollten keine Variablen sein . Aber im Nachhinein ist man immer schlauer, und die Java-Entwickler hatten damals alle Hände voll zu tun.

Fügen Sie also immer final auf alle Argumente?

Sollten wir hinzufügen final zu jedem einzelnen deklarierten Methodenparameter?

  • Theoretisch, ja.
  • In der Praxis: nein.
    hinzufügen final nur, wenn der Code der Methode lang oder kompliziert ist und das Argument fälschlicherweise für eine lokale oder Mitgliedsvariable gehalten und möglicherweise neu zugewiesen werden kann.

Wenn Sie der Praxis folgen, ein Argument niemals neu zuzuordnen, werden Sie dazu neigen, eine final für jeden. Aber das ist mühsam und macht die Erklärung etwas unübersichtlicher.

Bei kurzem, einfachem Code, bei dem das Argument offensichtlich ein Argument und weder eine lokale Variable noch eine Mitgliedsvariable ist, mache ich mir nicht die Mühe, den Zusatz final . Wenn der Code ziemlich offensichtlich ist und weder ich noch ein anderer Programmierer, der Wartungsarbeiten oder Refactoring durchführt, die Argumentvariable versehentlich als etwas anderes als ein Argument interpretieren kann, dann ist das nicht weiter schlimm. In meiner eigenen Arbeit, füge ich final nur in längerem oder kompliziertem Code, wo ein Argument fälschlicherweise für eine lokale oder Mitgliedsvariable gehalten werden könnte.

Ein weiterer Fall wurde der Vollständigkeit halber hinzugefügt.

public class MyClass {
    private int x;
    //getters and setters
}

void doSomething( final MyClass arg ) {  // Mark argument as 'final'.

   arg =  new MyClass();  // Compiler error: The passed argument variable arg  cannot be re-assigned to another object.

   arg.setX(20); // allowed
  // We can re-assign properties of argument which is marked as final
 }

record

Java 16 bringt die neue Datensätze Eigenschaft. Ein Datensatz ist eine sehr kurze Art, eine Klasse zu definieren, deren Hauptzweck darin besteht, Daten unveränderbar und transparent zu übertragen.

Sie deklarieren einfach den Klassennamen zusammen mit den Namen und Typen der Mitgliedsfelder. Der Compiler liefert implizit den Konstruktor, die Getter, equals & hashCode und toString .

Die Felder sind schreibgeschützt und haben keine Setzer. Also ein record ist ein Fall, in dem es nicht notwendig ist, die Argumente zu markieren final . Sie sind bereits effektiv endgültig. In der Tat verbietet der Compiler die Verwendung von final bei der Deklaration der Felder eines Datensatzes.

public record Employee( String name , LocalDate whenHired )  //  Marking `final` here is *not* allowed.
{
}

Wenn Sie einen optionalen Konstruktor bereitstellen, können Sie dort kann mark final .

public record Employee(String name , LocalDate whenHired)  //  Marking `final` here is *not* allowed.
{
    public Employee ( final String name , final LocalDate whenHired )  //  Marking `final` here *is* allowed.
    {
        this.name = name;
        whenHired = LocalDate.MIN;  //  Compiler error, because of `final`. 
        this.whenHired = whenHired;
    }
}

239voto

Jerry Sha Punkte 3751

Manchmal ist es gut, explizit darauf hinzuweisen (aus Gründen der Lesbarkeit), dass sich die Variable nicht ändert. Hier ist ein einfaches Beispiel, bei dem die Verwendung von final kann einige mögliche Kopfschmerzen ersparen:

public void setTest(String test) {
    test = test;
}

Wenn Sie das Schlüsselwort "this" bei einem Setter vergessen, wird die Variable, die Sie setzen wollen, nicht gesetzt. Wenn Sie jedoch das final Schlüsselwort auf den Parameter, dann würde der Fehler bei der Kompilierung abgefangen werden.

131voto

Jon Skeet Punkte 1325502

Ja, abgesehen von anonymen Klassen, Lesbarkeit und Absichtserklärung ist es fast wertlos. Aber sind diese drei Dinge wertlos?

Ich persönlich neige dazu, nicht zu verwenden final für lokale Variablen und Parameter, es sei denn, ich verwende die Variable in einer anonymen inneren Klasse, aber ich kann durchaus den Standpunkt derjenigen verstehen, die klarstellen wollen, dass sich der Parameterwert selbst nicht ändert (selbst wenn das Objekt, auf das er sich bezieht, seinen Inhalt ändert). Für diejenigen, die der Meinung sind, dass dies die Lesbarkeit erhöht, ist es meiner Meinung nach eine völlig vernünftige Sache, dies zu tun.

Ihr Argument wäre wichtiger, wenn jemand tatsächlich behaupten würde, dass es hat Daten auf eine Art und Weise konstant zu halten, wie es nicht der Fall ist - aber ich kann mich nicht daran erinnern, derartige Behauptungen gesehen zu haben. Wollen Sie damit sagen, dass es eine bedeutende Anzahl von Entwicklern gibt, die behaupten, dass final mehr Wirkung hat, als sie tatsächlich hat?

EDIT: Ich hätte das Ganze eigentlich mit einer Anspielung auf Monty Python zusammenfassen sollen; die Frage ähnelt ein wenig der Frage "Was haben die Römer je für uns getan?"

80voto

Michael Borgwardt Punkte 334642

Lassen Sie mich ein wenig über den einen Fall erzählen, in dem Sie haben um final zu verwenden, was Jon bereits erwähnt hat:

Wenn Sie eine anonyme innere Klasse in Ihrer Methode erstellen und eine lokale Variable (z. B. einen Methodenparameter) innerhalb dieser Klasse verwenden, zwingt Sie der Compiler, den Parameter als endgültig zu kennzeichnen:

public Iterator<Integer> createIntegerIterator(final int from, final int to)
{
    return new Iterator<Integer>(){
        int index = from;
        public Integer next()
        {
            return index++;
        }
        public boolean hasNext()
        {
            return index <= to;
        }
        // remove method omitted
    };
}

Hier wird die from y to Parameter müssen final sein, damit sie innerhalb der anonymen Klasse verwendet werden können.

Der Grund für diese Anforderung ist der folgende: Lokale Variablen befinden sich auf dem Stack, d.h. sie existieren nur, solange die Methode ausgeführt wird. Die anonyme Klasseninstanz wird jedoch von der Methode zurückgegeben, so dass sie viel länger existieren kann. Sie können den Stack nicht beibehalten, da er für nachfolgende Methodenaufrufe benötigt wird.

Was Java stattdessen tut, ist, die Kopien dieser lokalen Variablen als versteckte Instanzvariablen in der anonymen Klasse (Sie können sie sehen, wenn Sie den Bytecode untersuchen). Wären sie jedoch nicht final, könnte man erwarten, dass die anonyme Klasse und die Methode die Änderungen sehen, die die andere an der Variablen vornimmt. Um die Illusion aufrechtzuerhalten, dass es nur eine Variable und nicht zwei Kopien gibt, muss die Variable endgültig sein.

29voto

Fortyrunner Punkte 12559

Ich verwende final ständig für Parameter.

Bringt das so viel? Nicht wirklich.

Würde ich es ausschalten? Nein.

Der Grund: Ich fand 3 Fehler, bei denen Leute schlampigen Code geschrieben hatten und es versäumt hatten, eine Mitgliedsvariable in Accessors zu setzen. Alle Fehler waren schwer zu finden.

Ich würde es gerne sehen, wenn dies in einer zukünftigen Version von Java zum Standard gemacht würde. Die Sache mit der Übergabe nach Wert/Referenz verwirrt eine Menge junger Programmierer.

Eine weitere Sache.. meine Methoden neigen dazu, eine geringe Anzahl von Parametern haben, so dass der zusätzliche Text auf eine Methode Erklärung ist kein Problem.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X