メソッドパラメータで使用する場合、final
キーワードが本当に便利なのはなぜか理解できません。
匿名クラスの使用、可読性、意図の宣言を除けば、それはほとんど価値がないように思えます。
一部のデータが一定のままであることを強制することは、見た目ほど強力ではありません。
パラメータがプリミティブの場合、パラメータは値としてメソッドに渡されるため、変更してもスコープ外には影響がないため、効果はありません。
パラメータを参照渡しする場合、参照自体はローカル変数であり、メソッド内から参照が変更されても、メソッドスコープ外からは影響がありません。
以下の簡単なテスト例を考えてみましょう。メソッドが渡された参照の値を変更したにもかかわらず、このテストは成功しますが、効果はありません。
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;
}
ベストアンサー1
変数の再割り当てを停止する
これらの回答は知的に興味深いものですが、私は短くて簡単な回答を読んでいません。
変数が別のオブジェクトに再割り当てされるのをコンパイラが防止するようにしたい場合は、キーワードfinal を使用します。
変数が静的変数、メンバー変数、ローカル変数、引数/パラメータ変数のいずれであっても、効果はまったく同じです。
例
実際に効果を見てみましょう。
2 つの変数 ( argとx ) を両方とも異なるオブジェクトに再割り当てできるこの単純な方法を検討してください。
// 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.
}
ローカル変数をfinalとしてマークします。これにより、コンパイラ エラーが発生します。
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";
}
代わりに、パラメータ変数をfinalとしてマークしましょう。これもコンパイラ エラーになります。
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.
}
この話の教訓:
変数が常に同じオブジェクトを指すようにしたい場合は、変数をfinalとしてマークします。
引数を再割り当てしない
プログラミングのよい習慣として (どの言語でも)、呼び出しメソッドによって渡されたオブジェクト以外のオブジェクトにパラメータ/引数変数を再割り当てしないarg =
でください。上記の例では、行 を記述しないでください。人間は間違いを犯しますし、プログラマも人間なので、コンパイラに助けを求めましょう。すべてのパラメータ/引数変数を 'final' としてマークして、コンパイラがそのような再割り当てを見つけてフラグを付けられるようにします。
振り返ってみると
他の回答でも述べられているように…Java の元々の設計目標は、配列の末尾を超えて読み取るといったプログラマーの愚かなミスを回避することだったため、Java はすべてのパラメーター/引数変数を 'final' として自動的に強制するように設計されるべきでした。言い換えれば、引数は変数であってはなりません。しかし、後知恵は 20/20 の視力であり、当時の Java 設計者は手一杯でした。
それで、常にfinal
すべての引数に追加しますか?
final
宣言されている各メソッドパラメータに追加する必要がありますか?
- 理論的にはそうです。
- 実際には、そうではありません。➥メソッドのコードが長いか複雑で、引数がローカル変数またはメンバー変数と間違えられ、再割り当てされる可能性がある場合にのみ
追加します。final
引数を再割り当てしないという慣例に従う場合は、final
各引数に を追加したくなるでしょう。しかし、これは面倒で、宣言が読みにくくなります。
引数が明らかに引数であり、ローカル変数でもメンバー変数でもない短い単純なコードの場合、私は を追加しませんfinal
。コードが非常に明白で、メンテナンスやリファクタリングを行う私や他のプログラマーが引数変数を誤って引数以外のものと間違える可能性がない場合は、 を気にする必要はありません。私自身の作業では、引数final
がローカル変数またはメンバー変数と間違われる可能性のある、より長いコードまたはより複雑なコードにのみ を追加します。
#完全性のためにもう一つのケースを追加しました
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では新しい記録機能。レコードは、不変かつ透過的にデータを運ぶことだけを主な目的とするクラスを定義するための非常に簡潔な方法です。
クラス名とそのメンバー フィールドの名前および型を宣言するだけです。コンパイラは暗黙的にコンストラクター、ゲッター、equals
& hashCode
、およびを提供しますtoString
。
フィールドは読み取り専用で、セッターはありません。したがって、record
引数をマークする必要がない 1 つのケースは、すでに事実上 final です。実際、コンパイラはレコードのフィールドを宣言するときにfinal
を使用することを禁止しています。final
public record Employee( String name , LocalDate whenHired ) // �� Marking `final` here is *not* allowed.
{
}
オプションのコンストラクターを提供する場合は、そこに をマークできます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;
}
}