メソッドのパラメーターとローカル変数にfinalを使用する必要があるのはいつですか?


171

可能な限り使用することを提案する参考文献(たとえば)をいくつか見つけましたが、finalそれがどれほど重要か疑問に思っています。これは主に、メソッドパラメータとローカル変数のコンテキストであり、最終的なメソッドやクラスではありません。定数の場合、それは当然のことです。

一方では、コンパイラーはいくつかの最適化を行うことができ、プログラマーの意図をより明確にします。一方、冗長性が追加され、最適化は簡単な場合があります。

覚えておくべきなのでしょうか?


ここに目を通すに関連する投稿は次のとおりです。stackoverflow.com/questions/137868/...
mattlantは


1
これを読む前に、パラメーターの修飾子としてfinalを使用できることを知らなかったからといって、賛成票を投じています。ありがとう!
キップ

回答:


178

にこだわる:

  • 最終フィールド-フィールドを最終としてマークすると、建設の終わりまでに強制的に設定され、そのフィールド参照は不変になります。これにより、フィールドの安全な公開が可能になり、後で読み取るときに同期する必要がなくなります。(オブジェクト参照の場合、フィールド参照のみが不変であることに注意してください。オブジェクト参照が参照するものは変更可能であり、不変性に影響します。)
  • 最終的な静的フィールド-以前は静的な最終フィールドを使用していた多くの場合で、列挙型を使用しています。

検討するが慎重に使用する:

  • 最終クラス-私が検討するのは、フレームワーク/ API設計のみです。
  • 最終メソッド-基本的に最終クラスと同じです。クレイジーで最終的なものをマークするようなテンプレートメソッドパターンを使用している場合は、おそらく継承に依存しすぎて、委任に十分ではないでしょう。

肛門を感じない限り無視してください:

  • メソッドのパラメーターとローカル変数-怠惰でコードが雑然としていることが主な原因です。私が変更しないつもりのパラメータとローカル変数のマーキングが「より適切」であることを完全に認めます。それがデフォルトだったらいいのに。しかし、それはそうではなく、ファイナル全体でコードを理解するのはより難しいと感じています。私が他の誰かのコードにいる場合、私はそれらを引き出すつもりはありませんが、新しいコードを書いている場合、それらを入れません。1つの例外は、アクセスできるように何かを最終的にマークする必要がある場合です。匿名の内部クラス内から。

  • 編集:@ adam-gentで言及されているように、最終的なローカル変数が実際に非常に役立つユースケースの1つは、値がif/ elseブランチのvarに割り当てられる場合です。


8
Joshua Blochは、継承のために設計されていない限り、すべてのクラスをfinalとして定義する必要があると主張しています。私は彼に同意します。インターフェースを実装するすべてのクラスにfinalを追加します(ユニットテストを作成できるようにするため)。また、オーバーライドされないすべての保護/クラスメソッドをfinalとしてマークします。
rmaruszewski、2008年

61
Josh Blochに敬意を払って(そしてそれはかなりの量です)、私は一般的なケースに同意しません。APIを作成する場合は、必ずロックしてください。自分のコードの中にBuiを追加し、後で取り壊さなければならない壁を建てるのは時間の無駄です。
Alex Miller

9
それは間違いなく「時間の浪費」ではありません。特に、まったく時間がかからないためです...アプリケーションでは、通常、ほとんどすべてのクラスfinalをデフォルトで作成します。ただし、本当に最新のJava IDE(つまり、IDEA)を使用しない限り、そのメリットに気付かない場合があります。
ホジェリオ

9
IDEAには(箱から出して)何百ものコード検査があり、それらのいくつかはfinalクラス/メソッド内の未使用/不要なコードを検出できます。たとえば、最終メソッドがチェック済み例外をスローするように宣言しているが、実際にはスローしない場合、IDEAはそれを通知し、throws節から例外を削除できます。時には、未使用のメソッド全体を見つけることもできます。これは、オーバーライドできないときに検出可能です。
ホジェリオ

8
@rmaruszewskiクラスを最終としてマークすると、ほとんどのモックフレームワークがモックを作成できなくなり、コードのテストが困難になります。クラスを延長しないことが非常に重要である場合にのみ、クラスをfinalにします。
Mike Rylander、2014

44

覚えておくべきことはありますか?

いいえ。Eclipseを使用している場合は、保存アクションを構成して、これらの最終修飾子を自動的に追加できます。次に、少ない労力でメリットを得ます。


3
保存アクションに関するすばらしいヒント、それについては知りませんでした。
正気

1
私は主にfinalがコードをバグからより安全にする利点を、発生するかもしれないし発生しないかもしれない最適化ではなく、間違った変数に誤って割り当てることを考慮しています。
ピーター・ヒルトン

4
これは本当にあなたにとって問題ですか?これに起因するバグが実際にどのくらいの頻度で発生しましたか?
Alex Miller

1
Eclipseで役立つヒントの+1。バグを避けるために、できるだけfinalを使用する必要があると思います。
emeraldhieu

IntelliJでもこれを行うことができますか?
Koray Tugay

15

「最終」の開発時の利点は、少なくとも実行時の利点と同じくらい重要です。それはあなたの意図についてコードの将来の編集者に何かを伝えます。

クラスに「最終」のマークを付けると、クラスの設計または実装中に拡張を適切に処理するための努力をしなかったことを示します。読者がクラスに変更を加えることができ、「最終」修飾子を削除したい場合は、自己の責任において行うことができます。クラスが拡張機能を適切に処理できるかどうかは、それらの責任です。

変数に「final」のマークを付ける(およびコンストラクターで割り当てる)と、依存関係の注入に役立ちます。変数の「コラボレーター」の性質を示します。

メソッドに「final」のマークを付けると、抽象クラスで役立ちます。拡張ポイントがどこにあるかを明確に示します。


11

私はfinalいつもJavaをより表現ベースにするために使用しています。Javaの条件(if,else,switch)は式に基づいていないので、関数型プログラミング(ML、Scala、Lispなど)に慣れている場合は特に嫌いです。

したがって、条件を使用するときは常に(IMHO)最終変数を使用するようにしてください。

例を挙げましょう。

    final String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
        default:
            throw new IllegalStateException();
    }

ここで、別のcaseステートメントを追加して設定しないとname、コンパイラは失敗します。(変数を設定した)すべてのケースで中断しないと、コンパイラーも失敗します。これにより、JavaをLispのlet式に非常に似たものにすることができ、コードが過度にインデントされないようにします(字句スコープ変数のため)。

そして、@ Recurseが述べたように(しかし私にはどうやら-1私)String name final、コンパイラエラーを取得することなく上記のことを行うことができます(私はあなたができなかったとは決して言えません)が、簡単にコンパイラエラーがスイッチの後に名前を設定するのをやめることができました式のセマンティクスをbreak破棄するステートメント、または(@Recurseの内容にかかわらず)エラーを発生させずに、final次のことを忘れると、

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            //break; whoops forgot break.. 
            //this will cause a compile error for final ;P @Recurse
        case JOB_POSTING_IMPORT:
            name = "Blah";
            break;
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

バグ設定名breakが原因で(別のバグも忘れるだけでなく)、誤ってこれを行うことができます。

    String name;
    switch(pluginType) {
        case CANDIDATE_EXPORT:
            name = "Candidate Stuff";
            break;
        //should have handled all the cases for pluginType
    }
    // code, code, code
    // Below is not possible with final
    name = "Whoops bug";

最後の変数は、名前がどうあるべきかについて単一の評価を強制します。戻り値を持つ関数が常に値を返す必要がある方法と同様に(例外を無視して)、名前切り替えブロックは名前を解決する必要があるため、そのスイッチブロックにバインドされ、コードのチャンクのリファクタリングが容易になります(つまり、Eclipeリファクタリング:抽出メソッド)。 。

上記のOCaml:

type plugin = CandidateExport | JobPostingImport

let p = CandidateExport

let name = match p with
    | CandidateExport -> "Candidate Stuff"
    | JobPostingImport -> "Blah" ;;

match ... with ...関数すなわち式のように評価します。switchステートメントのように見えることに注意してください。

以下は、Scheme(RacketまたはChicken)の例です。

(define name 
    (match b
      ['CandidateExport "Candidate Stuff"]
      ['JobPostingImport "Blah"]))

1
ただし、Javaコンパイラはすでに「名前が初期化されていない可能性がある」というエラーを表示し、ファイナルの有無にかかわらずコンパイルを拒否します。
2014

4
はい、しかし決勝戦がなければ、いつでもリセットできます。私は実際に、開発者がをに変えて変数をリセットelse if (...)したときにこれが起こるのを見ましたif(...)。私は、最終的な変数では決して起こらないであろうことを彼に示しました。基本的finalには、変数を1回だけ割り当てるように強制します... so:P
Adam Gent

9

final問題のメソッドが数ページにわからないほど混乱している場合に、リファクタリングの補助として役立つメソッドパラメータとローカルのマーキングを見つけました。final自由に振りかけ、コンパイラ(またはIDE)がスローする「最終変数に割り当てられない」エラーを確認します。いくつかの(古い)コメントで、「data」という変数がnullになる理由がわかる場合があります。起こらない。

次に、再利用された変数を、使用場所の近くで宣言された新しい変数に置き換えることで、いくつかのエラーを修正できます。次に、メソッドのすべての部分をスコープブレースで囲むことができます。突然、「抽出メソッド」から離れた1つのIDEキーを押すだけになり、モンスターがより理解しやすくなりました。

あなたの方法がまだ維持不可能な問題でない場合、私は人々がそれをその問題に変えるのを思いとどまらせるようなものを最終的にすることに価値があるかもしれないと思います。しかし、それが短い方法である場合(参照:保守不可能ではない)、多くの冗長性を追加するリスクがあります。特に、Java関数のシグニチャーは、引数ごとに6つ追加することなく、80文字に収まるほど十分に困難です。


1
最後のポイントは非常に有効ですが、過去10年間で画面の解像度が少し変わったため、80文字の制限をあきらめました。スクロールせずに画面に300文字の行を簡単に合わせることができます。それにもかかわらず、当然のことながら、finalbefore beforeパラメータを使用しないと読みやすさが向上します。
ブリンボリウム2013年

8

まあ、これはすべてあなたのスタイルに依存します...変数を変更しないときにファイナルを見るのが好きなら、それを使用してください。見たくない場合は...

個人的には、できるだけ冗長性を抑えたいので、あまり必要のない余分なキーワードは使わないようにしています。

でも私は動的言語を好むので、冗長を避けたいのは当然のことです。

だから、私はあなたが傾いている方向を選んでそれと一緒に行くだけだと思います(いずれにしても、一貫性を保つようにしてください)。


余談ですが、私はそのようなパターンを使用するプロジェクトと使用しないプロジェクトの両方で作業しましたが、バグやエラーの量に違いはありませんでした。バグの数などを改善しますが、これもスタイルです。変更しないという意図を表明したい場合は、先に進んで使用してください。


6

パラメータで誤ってパラメータ値を変更することを避け、微妙なバグを導入するのに役立ちます。私はこの推奨を無視するために使用していますが、約4時間を費やした後です。恐ろしい方法で(何百行ものコードと複数のfors、ネストされたifsとあらゆる種類の悪い慣行で)私はそれを行うことをお勧めします。

 public int processSomethingCritical( final int x, final int y ){
 // hundreds of lines here 
     // for loop here...
         int x2 = 0;
        x++; // bug aarrgg...
 // hundreds of lines there
 // if( x == 0 ) { ...

 }

もちろん、完璧な世界ではこれは起こりませんが、他のコードをサポートする必要がある場合もあります。:(


2
この方法には、finalがない場合よりも深刻な問題があります。メソッドが乱雑になってこのような種類のエラーが発生する正当な理由があることは、不可能ではありませんが、かなり一般的ではありません。変数名を少し考えれば、このような事故に大いに役立ちます。
ykaganovich 2008年

2
1つのメソッドに「数百行のコード」がある場合、それをいくつかの小さなメソッドに分割することができます。
Steve Kuo

5

たとえば1年後に誰かがコードを読み取る必要があるアプリケーションを作成している場合は、はい、常に変更する必要のない変数のfinalを使用します。これにより、コードはより「自己文書化」され、ローカル定数をローカル一時変数として使用するなど、他の開発者が愚かなことをする可能性も減少します。

使い捨てのコードを作成している場合は、すべての定数を特定してそれらを最終的なものにする必要はありません。


4

ファイナルはなるべく使います。そうすると、フィールドを誤って変更した場合にフラグが立てられます。また、メソッドパラメータをfinalに設定しました。そうすることで、Javaが値で渡すのを忘れてパラメータを「設定」しようとしたときに引き継いだコードからいくつかのバグを見つけました。


2

これが明白であるかどうかの質問からは明らかではありませんが、メソッドパラメーターをfinalにしても、メソッドの本体にのみ影響します。メソッドの意図に関する興味深い情報を呼び出し元に伝えることはありません。渡されるオブジェクトはメソッド内で変更でき(最終はconstではありません)、変数のスコープはメソッド内にあります。

あなたの正確な質問に答えるために、コードがそれを必要としない限り(例:変数が内部クラスから参照されている場合)、またはいくつかの本当に複雑なロジックを明確にするために、インスタンスまたはローカル変数(メソッドパラメーターを含む)をfinalにしたりしません。

インスタンス変数の場合、それらが論理的に定数であれば、それらをfinalにします。


2

変数には多くの用途がありますfinal。ここにいくつかあります

最終定数

 public static class CircleToolsBetter {
     public final static double PI = 3.141;
        public double getCircleArea(final double radius) {
          return (Math.pow(radius, 2) * PI);
        }
    }

これは、コードの他の部分に使用したり、他のクラスからアクセスしたりできるため、値を変更する場合でも、1つずつ変更する必要はありません。

最終変数

public static String someMethod(final String environmentKey) {
    final String key = "env." + environmentKey;
    System.out.println("Key is: " + key);
    return (System.getProperty(key));

  }

}

このクラスでは、パラメーターenvironmentKeyにプレフィックスを追加するスコープ付き最終変数を作成します。この場合、最後の変数は、メソッドの実行ごとに異なる実行スコープ内でのみfinalになります。メソッドに入るたびに、ファイナルが再構築されます。作成されるとすぐに、メソッド実行のスコープ中に変更することはできません。これにより、メソッドの期間中、メソッド内の変数を修正できます。下記参照:

public class FinalVariables {


  public final static void main(final String[] args) {
    System.out.println("Note how the key variable is changed.");
    someMethod("JAVA_HOME");
    someMethod("ANT_HOME");
  }
}

最終定数

public double equation2Better(final double inputValue) {
    final double K = 1.414;
    final double X = 45.0;

double result = (((Math.pow(inputValue, 3.0d) * K) + X) * M);
double powInputValue = 0;         
if (result > 360) {
  powInputValue = X * Math.sin(result); 
} else {
  inputValue = K * Math.sin(result);   // <= Compiler error   
}

これらは、コードの行が非常に長い場合に特に役立ち、コンパイラエラーを生成するので、変更してはならない変数を誤って変更したときにロジック/ビジネスエラーが発生することはありません。

最終コレクション

コレクションについて話している場合とは異なり、変更不可として設定する必要があります。

 public final static Set VALID_COLORS; 
    static {
      Set temp = new HashSet( );
      temp.add(Color.red);
      temp.add(Color.orange);
      temp.add(Color.yellow);
      temp.add(Color.green);
      temp.add(Color.blue);
      temp.add(Color.decode("#4B0082")); // indigo
      temp.add(Color.decode("#8A2BE2")); // violet
      VALID_COLORS = Collections.unmodifiableSet(temp);
    }

それ以外の場合、変更不可として設定しないと、次のようになります。

Set colors = Rainbow.VALID_COLORS;
colors.add(Color.black); // <= logic error but allowed by compiler

最終クラス最終メソッドは、それぞれ拡張または上書きできません。

編集:カプセル化に関する最終的なクラスの問題に対処するには:

クラスをファイナルにする方法は2つあります。1つ目は、クラス宣言でキーワードfinalを使用することです。

public final class SomeClass {
  //  . . . Class contents
}

クラスをfinalにする2番目の方法は、すべてのコンストラクターをプライベートとして宣言することです。

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

最終とマークすることで、それが実際の最終であることが判明した場合に、このTestクラスを確認する手間を省くことができます。一見して公共のように見えます。

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

残念ながら、クラスの唯一のコンストラクターはプライベートなので、このクラスを拡張することは不可能です。Testクラスの場合、クラスがfinalである必要はありません。Testクラスは、暗黙の最終クラスが問題を引き起こす可能性のある良い例です。

そのため、コンストラクターをプライベートにすることでクラスを暗黙的にfinalにする場合は、finalとマークする必要があります。


1

あなたが言及するようにある程度のトレードオフですが、私は暗黙的な使用よりも何かの明示的な使用を好みます。これは、たとえそれがあなただけの場合でも、コードの将来のメンテナーにとって曖昧さを取り除くのに役立ちます。


1

内部(匿名)クラスがあり、メソッドが包含メソッドの変数にアクセスする必要がある場合は、その変数をfinalにする必要があります。

それ以外は、あなたの言ったことは正しいです。


現在、Java 8は効果的な最終変数で柔軟性を提供しています。
Ravindra babu 2015年

0

final変数を次のように作成する場合は、変数にキーワードを使用しますimmutable

変数をfinalとして宣言することにより、高度なマルチスレッド環境での変数の可能な変更の問題を除外するのに役立ちます。

Java 8リリースでは、「effectively final variable」と呼ばれるもう1つのコンセプトがあります。非最終変数は、最終変数として盛り上がる可能性があります。

ラムダ式から参照されるローカル変数はfinalまたは事実上finalでなければなりません

変数は、ローカルブロックでの初期化後に変更されない場合、有効な最終版と見なされます。これは、匿名クラスまたはラムダ式内でfinalキーワードなしでローカル変数を使用できることを意味します。ただし、それらは事実上finalでなければなりません。

Java 7までは、匿名クラス内でfinal以外のローカル変数を使用できませんが、Java 8からはできます

この記事をご覧ください


-1

まず、最後のキーワードは変数を定数にするために使用されます。一定とは、変化しないことを意味します。例えば:

final int CM_PER_INCH = 2.54;

1インチあたりのセンチメートルは変化しないため、変数finalを宣言します。

最終値をオーバーライドしようとすると、変数は最初に宣言されたものになります。例えば:

final String helloworld = "Hello World";
helloworld = "A String"; //helloworld still equals "Hello World"

次のようなコンパイルエラーがあります。

local variable is accessed from inner class, must be declared final

変数をfinalと宣言できない場合、またはfinalと宣言したくない場合は、以下を試してください。

final String[] helloworld = new String[1];
helloworld[0] = "Hello World!";
System.out.println(helloworld[0]);
helloworld[0] = "A String";
System.out.println(helloworld[0]);

これは印刷されます:

Hello World!
A String
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.