変数の再割り当てを停止する
これらの回答は知的に興味深いものですが、短い簡単な回答は読んでいません。
変数が別のオブジェクトに再割り当てされるのをコンパイラに防止させたい場合は、キーワード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 =
。人間は間違いを犯し、プログラマーも人間なので、コンパイラーに助けを求めましょう。すべてのパラメーター/引数変数を「最終」としてマークして、コンパイラーがそのような再割り当てを見つけてフラグを立てることができるようにします。
振り返ってみると
他の回答で述べたように... プログラマーが配列の末尾を越えて読み取るなどのばかげたミスを回避できるようにするという Java の当初の設計目標を考えると、Java はすべてのパラメーター/引数変数を「最終」として自動的に強制するように設計されている必要があります。つまり、Arguments は variables であってはなりません。しかし、後から考えると 20/20 のビジョンであり、Java 設計者は当時手一杯でした。
final
では、常にすべての引数に追加しますか?
final
宣言されているすべてのメソッド パラメータに追加する必要がありますか?
- 理論的には、そうです。
- 実際には、いいえ。
➥final
メソッドのコードが長いか複雑で、引数がローカル変数またはメンバー変数と間違えられ、再割り当てされる可能性がある場合にのみ追加します。
引数を再割り当てしないという慣行を受け入れると、final
それぞれに a を追加する傾向があります。しかし、これは面倒で、宣言が読みにくくなります。
引数が明らかに引数であり、ローカル変数でもメンバー変数でもない短い単純なコードの場合、わざわざ . を追加する必要はありません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
。
フィールドは読み取り専用で、セッターはありません。したがって、arecord
は引数をマークする必要がない 1 つのケース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;
}
}