Javaでは、変数(フィールド/ローカル/パラメータ)をfinal
としてマークして、それらに再割り当てされないようにすることができます。一部の属性(またはクラス全体)が不変であることが意図されているかどうかをすばやく確認できるので、フィールドを使用すると非常に便利です。
一方、ローカルやパラメーターではあまり役に立たないことがわかります。通常、それらがfinal
として再割り当てされない場合でも、それらをマークしないようにします(内部クラスで使用する必要がある場合は明らかな例外があります)。 )。しかし、最近、可能であればいつでもfinalを使用するコードに出会いました。技術的には、より多くの情報を提供していると思います。
私のプログラミングスタイルに自信が持てなくなったので、final
をどこにでも適用することの他の利点と欠点、最も一般的な業界スタイル、およびその理由は何でしょうか。
私はあなたと同じようにfinal
を使います。私にとっては、ローカル変数とメソッドパラメータは不必要に見え、有用な追加情報を伝えていません。
重要なことの1つは、各メソッドが1つのタスクを実行する私のメソッドを短く簡潔にするに努めることです。したがって、私のローカル変数とパラメーターのスコープは非常に限定されており、単一の目的でのみ使用されます。これにより、不注意でそれらを再割り当てする可能性が最小限に抑えられます。
さらに、ご存じのとおり、final
は、(非プリミティブ)変数の値/状態を変更できないことを保証しません。初期化すると、referenceをそのオブジェクトに再割り当てできないことだけがわかります。つまり、プリミティブ型または不変型の変数でのみシームレスに機能します。検討する
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
これは、多くの場合、コード内で何が起こっているのかを理解することがまだ容易ではないことを意味します。これを誤解すると、実際には検出が難しい微妙なバグが発生する可能性があります。
あなたのJavaプログラミングスタイルと考えは問題ありません-自分を疑う必要はありません。
一方、ローカルとパラメーターではあまり役に立たないことがわかります。通常、それらが再割り当てされない場合でも、最終的なものとしてマークすることは避けます(内部クラスで使用する必要がある場合は、明らかな例外があります)。 )。
これが、final
キーワードを使用する理由です。あなたは[〜#〜] you [〜#〜]は再割り当てされないことを知っていると述べていますが、他の誰もそれを知りません。 final
を使用すると、コードがすぐに明確になります。
可能な限りfinal
/const
を使用する利点の1つは、コードの読者の精神的な負荷が軽減されることです。
値/参照が後で変更されることはないため、彼は安心できます。したがって、彼は計算を理解するために修正に注意を払う必要はありません。
純粋関数型プログラミング言語を学んだ後、私はこれについて私の考えを変えました。 「変数」を信頼して、常にその初期値を保持できるとしたら、なんと安心でしょう。
メソッドパラメーターとローカル変数のfinal
をコードノイズと見なしています。 Javaメソッド宣言は非常に長くなる可能性があります(特にジェネリックの場合)-それらを長くする必要はありません。
単体テストが適切に記述されている場合、「有害」なパラメーターへの割り当てが検出されるため、決して実際にはが問題になることはありません。単体テストのカバレッジが不十分であるためにピックアップされないpossibleバグを回避するよりも、視覚的な明快さの方が重要です。
FindBugsやCheckStyleのようなツールは、パラメーターやローカル変数に割り当てが行われた場合にビルドを中断するように構成できます。
もちろん、needを使用してそれらを最終的なものにする場合、たとえば、匿名クラスで値を使用している場合は問題ありません。これが最も簡単でクリーンなソリューションです。
パラメータに余分なキーワードを追加し、それによってIMHOを偽装する明らかな影響とは別に、finalをメソッドパラメータに追加すると、多くの場合、メソッド本体のコードが読みにくくなり、コードが悪化します。できるだけ読みやすく、シンプルでなければなりません。不自然な例として、大文字と小文字を区別せずに動作する必要があるメソッドがあるとします。
final
なし:
_public void doSomething(String input) {
input = input.toLowerCase();
// do a few things with input
}
_
シンプル。掃除。誰もが何が起こっているか知っています。
ここで「最終」、オプション1を使用します。
_public void doSomething(final String input) {
final String lowercaseInput = input.toLowerCase();
// do a few things with lowercaseInput
}
_
パラメータをfinal
にすると、コーダーがコードを追加して元の値で作業していると考えることができなくなりますが、コードがさらに下にinput
の代わりにlowercaseInput
を使用する可能性があるという同じリスクがあります。スコープから外すことはできません(あるいは、null
をinput
に割り当てても、とにかく役立つ場合があります)。
「最終」の場合、オプション2:
_public void doSomething(final String input) {
// do a few things with input.toLowerCase()
}
_
これで、さらに多くのコードノイズが作成され、toLowerCase()
をn回呼び出す必要があるというパフォーマンスヒットが発生しました。
「最終」の場合、オプション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
}
_
コードノイズについて話します。これは列車事故です。他のコードが誤って呼び出す可能性があるため、必要な例外ブロックである新しいメソッドを用意しました。例外をカバーするためのより多くの単体テスト。すべてが1つの単純で、IMHOの好ましい無害な行を避けるためです。
また、メソッドがそれほど長くてはならず、視覚的に簡単に取り込むことができず、パラメータへの割り当てが行われたことを一目で把握できないという問題もあります。
パラメータに割り当てる場合は、メソッドの早い段階で、できれば最初の行または基本的な入力チェックの直後に実行するのが良い習慣/スタイルだと思います置き換え全体メソッド。メソッド内で一貫した効果があります。読者は、割り当てが明白(署名宣言の近く)で一貫した場所にあることを期待しているため、finalを追加することで回避しようとしている問題が大幅に軽減されます。実際にパラメーターを割り当てることはめったにありませんが、割り当てる場合は常にメソッドの先頭で行います。
また、final
は、最初は思われるかもしれないように、実際にはユーザーを保護しません。
_public void foo(final Date date) {
date.setTime(0);
// code that uses date
}
_
final
は、パラメーターの型がプリミティブまたは不変でない限り、完全には保護されません。
プログラムを読みやすくするために考慮しているため、Eclipseに各ローカル変数の前にfinal
を付けさせています。パラメーターリストはできるだけ短くしたいので、パラメーターを使用して作成しません。理想的には、1行に収まるようにします。