Androidのいくつかのクラスを調べたところ、メソッドの変数のほとんどがfinalとして宣言されていることに気付きました。
クラスAndroid.widget.ListViewから取得したコードの例:
/**
* @return Whether the list needs to show the top fading Edge
*/
private boolean showingTopFadingEdge() {
final int listTop = mScrollY + mListPadding.top;
return (mFirstPosition > 0) || (getChildAt(0).getTop() > listTop);
}
/**
* @return Whether the list needs to show the bottom fading Edge
*/
private boolean showingBottomFadingEdge() {
final int childCount = getChildCount();
final int bottomOfBottomChild = getChildAt(childCount - 1).getBottom();
final int lastVisiblePosition = mFirstPosition + childCount - 1;
final int listBottom = mScrollY + getHeight() - mListPadding.bottom;
return (lastVisiblePosition < mItemCount - 1)
|| (bottomOfBottomChild < listBottom);
}
これらのケースでfinalキーワードを使用する意図は何ですか?
これは習慣の力によるものだと思います。このコードを書いたプログラマーは、コードを書いているときに、最終的な変数の値は代入後に変更してはならないので、それらを最終的なものにすることを知っていました。割り当て後に新しい値を最終変数に割り当てようとすると、コンパイラエラーが発生します。
習慣が進むにつれ、発達することは悪いことではありません。少なくとも、変数をfinalにすることは、執筆時のプログラマーのintentを指定します。これは、コードを編集する後続のプログラマーが、その変数の使用方法を変更し始める前に考えのために一時停止する可能性があるため、重要です。
Javaデフォルトですべての変数final
を作成する開発者(そしてEclipseがこれを自動的に実行できるという事実を高く評価している開発者)と言えば、変数は一度初期化され、再度変更されることはありません。
1つには、初期化されていない変数は問題ではなくなりました。初期化される前にfinal
変数を使用しようとすると、コンパイルエラーが発生するためです。これは、すべてのケースをカバーしたことを確認したいネストされた条件ロジックに特に役立ちます。
final int result;
if (/* something */) {
if (/* something else */) {
result = 1;
}
else if (/* some other thing */) {
result = 2;
}
}
else {
result = 3;
}
System.out.println(result);
すべてのケースをカバーしましたか? (ヒント:いいえ)案の定、このコードはコンパイルすらできません。
もう1つ:一般に、変数について何かが常に真であることを知っているときはいつでも、言語にそれを強制するように試みるべきです。もちろん、これは毎日変数の型を指定するときに行われます。言語は、その型ではない値がその変数に格納されないようにします。同様に、メソッド全体で保持する必要のある値がすでに変数に割り当てられているために変数を再割り当てしてはならないことがわかっている場合は、final
と宣言することで言語にその制限を適用できます。
最後に、習慣の問題があります。他の人はこれが習慣(+1から そのためのジョン )であると述べましたが、なぜこの習慣が必要なのかについて少し触れさせてください。メソッドのローカル変数ではなくクラスのフィールドを宣言している場合、複数のスレッドがそれらのフィールドに同時にアクセスする可能性があります。いくつかのあいまいな例外がありますが、一般に、フィールドがfinalの場合、クラスを使用するすべてのスレッドは、変数に対して同じ値を参照します。逆に、フィールドがfinalではなく、複数のスレッドがクラスを使用している場合は、synchronized
ブロックまたはJava.util.concurrent
のクラスを使用した明示的な同期について心配する必要があります。同期は可能ですが、プログラミングはすでに十分に困難です。 ;-)したがって、everythingfinal
を習慣から外して宣言すると、多くのフィールドが最終的なものになり、できるだけ少ない時間を費やします同期および同時実行関連のバグについて心配している。
この習慣について詳しくは、Joshua Blochの Effective Java の「最小化の可変性」のヒントをご覧ください。
編集:@Peter Taylorは、final
キーワードを削除すると上記の例もコンパイルされないことを指摘しましたが、これは完全に正しいことです。すべてのローカル変数を最終的に維持することを推奨したとき、それは次のような例を不可能にしたかったからです。
int result = 0;
// OK, time to cover all the cases!
if (/* something */) {
if (/* something else */) {
result = 1;
}
else if (/* some other thing */) {
result = 2;
}
// Whoops, missed an "else" here. Too bad.
}
else {
result = 3;
}
System.out.println(result); // Works fine!
古い変数を再利用する代わりに新しい変数を使用することで、可能性の全領域をカバーしようとすることをコンパイラーに伝えることができます。final
変数を使用すると、古い変数をリサイクルする代わりに新しい変数を使用する必要があります。
この例についてのもう1つの有効な不満は、最初から複雑なネストされた条件付きロジックを回避する必要があることです。もちろん、それは本当です。それは、意図した方法ですべてのケースを確実にカバーするのが難しいためです。ただし、複雑なロジックを回避できない場合があります。ロジックが複雑な場合は、変数をできるだけ簡単に推論できるようにします。これは、変数の値が初期化後に変更されないようにすることで実現できます。
(元の開発者に尋ねない限り)確かに答えはわかりませんが、私の推測は次のとおりです。
もう1つの理由は、内部の匿名クラスでそれらを使用できるようにするためです。
public interface MyInterface {
void foo();
}
void myMethod() {
final String asdf = "tada";//if not final cannot be used in inner classes
new MyInterface() {
@Override
public void foo() {
String myNewString = asdf;//does not compile only if asdf is final
}
}
}
愚かな間違いの頻度を減らすため。 final
は、変数が常に最初に割り当てられたオブジェクトを指すことを確認します。試みられた変更はコンパイル時エラーとしてカウントされます。
また、「なぜ変数の型を明示的に宣言するのか」や「C++でメソッドを定数として宣言する理由」と尋ねることもできます。同じ理由。
ここに理由:ローカル変数/パラメーターへの再割り当てを回避します。これにより、読みやすさが向上し、いくつかの愚かなバグが回避されます。
http://sourcemaking.com/refactoring/remove-assignments-to-parameters
抜粋:
Java 1.1以降のバージョンでは、パラメーターをfinalとしてマークできます。これにより、変数への割り当てが防止されます。それでも、変数が参照するオブジェクトを変更できます。私は常に自分のパラメーターを最終的なものとして扱いますが、パラメーターリストでそのようにマークすることはめったにありません。
正直なところ、この文脈で変数をfinalにする理由は本当はないと思います。彼らはコードをコピーして貼り付けたか、そのメソッドにさらにコードを追加する人に、それらの変数への再割り当てをさせないようにしたいと思っています。