Javaのメソッドパラメータで「final」というキーワードを使用する必要があるのはなぜですか?


381

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;
}

101
用語の要点-Javaには参照渡しがまったくありません。同じではない値による参照渡しがあります。参照渡しのセマンティクスが真の場合、コードの結果は異なります。
Jon Skeet、

6
参照渡しと値渡し参照の違いは何ですか?
NobleUplift 2013年

(少なくとも私にとっては)Cのコンテキストでその違いを説明する方が簡単です。<code> int foo(int bar)</ code>のようなメソッドへのポインターを渡すと、そのポインターは値で渡されます。それがコピーされるという意味なので、<code> free(bar); bar = malloc(...); </ code>次に、本当に悪いことをしました。free呼び出しは、実際にポイントされているメモリのチャンクを解放します(そのため、私が渡したポインターがぶら下がっています)。ただし、<code> int foo(int&bar)</ bar>は、コードが有効であり、渡されたポインタの値が変更されることを意味します。
jerslan 2013年

1
最初のものはそうint foo(int* bar)で、最後のものint foo(int* &bar)です。後者はポインタを参照で渡し、前者は参照を値で渡します。
jerslan 2013年

2
@Martin、私の意見では、それは良い質問です。質問のタイトル、および理由の説明として、ポスト内容参照質問が尋ねているが。多分私はここのルールを誤解しているかもしれませんが、これはまさに「メソッドでの最終パラメータの使用」を検索するときに私が欲しかった質問です。
Victor Zamanian 2016年

回答:


239

変数の再割り当てを停止する

これらの回答は知的に興味深いものですが、短い簡単な回答は読んでいません。

変数が別のオブジェクトに再割り当てされないようにコンパイラーにしたい場合は、キーワードfinalを使用します。

変数が静的変数、メンバー変数、ローカル変数、引数/パラメーター変数のいずれであっても、効果はまったく同じです。

実際の効果を見てみましょう。

2つの変数(argx)の両方に異なるオブジェクトを再割り当てできる、この単純な方法を考えてみましょう。

// 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はすべてのパラメーター/引数変数を「最終」として自動的に強制するように設計されているはずです。つまり、引数は変数であってはなりません。しかし後知恵は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
 }

23
「(あらゆる言語での)良いプログラミング方法として、パラメーター/引数変数[..]を再割り当てしないでください」申し訳ありませんが、この変数については、必ず声をかけなければなりません。引数の再割り当ては、JavaScriptなどの言語では標準的な方法であり、渡される引数の数(または渡された引数があったとしても)はメソッドのシグネチャによって決定されません。たとえば、「function say(msg)」のようなシグネチャが与えられた場合、人々は引数「msg」が割り当てられることを確認します。たとえば、「msg = msg || 'Hello World!';」のようにします。世界最高のJavascriptプログラマーがあなたの良い習慣を打ち破っています。jQueryソースを読んでください。
Stijn de Witt

37
@StijndeWittこの例は、引数変数の再割り当ての問題そのものを示しています。あなたと一緒に失う情報獲得何も見返りに:()あなたは、元の値は、あなたが呼び出すメソッド(「!Hello Worldの」呼び出し側のパスをしたか、我々はデフォルトでした)の意思を失ってしまった(b)は、渡さ失ってしまいました。aとbはどちらも、テスト、長いコード、および後で値がさらに変更されるときに役立ちます。私は自分の声明を支持します:arg varsは決して再割り当てしないでください。コードは次のようになりますmessage = ( msg || 'Hello World"' )。別の変数を使用しない理由はありません。唯一のコストは、数バイトのメモリです。
バジルブルク2013年

9
@Basil:カウントするのは、より多くのコード(バイト単位)とJavaScriptです。かなり。多くのことと同様に、それは意見に基づいています。このプログラミング方法を完全に無視して、優れたコードを書くことは完全に可能です。一人のプログラミングの練習はそれを全員の練習にするわけではありません。どうぞよろしくお願いします。とにかく別の方法で書くことにしました。それは私を悪いプログラマーにしますか、それとも私のコードを悪いコードにしますか?
Stijn de Witt 2014

14
使用message = ( msg || 'Hello World"' )すると、後で誤ってを使用する危険性がありますmsg。私が意図しているコントラクトが「引数がない/ null /未定義の動作は渡すことと区別できない"Hello World"」である場合、関数の早い段階でそれにコミットすることは良いプログラミング習慣です。[これは、最初から再割り当てなしで実現できますがif (!msg) return myfunc("Hello World");、複数の引数を使用すると扱いにくくなります。]関数のロジックでデフォルトが使用されたかどうかを気にする必要があるまれなケースでは、特別なセンチネル値(できればパブリック)を指定します。
Beni Cherniavsky-Paskin 2015年

6
BeniCherniavsky-Paskin @あなたが記述危険性があるためとの類似だけであるmessagemsg。しかし、彼がprocessedMsgそれを追加のコンテキストを提供するようなものまたは他の何かと呼ぶ場合、間違いの可能性ははるかに低くなります。彼が言う「方法」ではなく、彼が言うことに焦点を合わせてください。;)
alfasin 2016年

230

変数が変化しないことを(読みやすくするために)明示的にするとよい場合があります。以下は、使用finalすることで頭痛の種を減らすことができる簡単な例です。

public void setTest(String test) {
    test = test;
}

セッターで「this」キーワードを忘れた場合、設定する変数は設定されません。ただし、finalパラメーターでキーワードを使用した場合、バグはコンパイル時に検出されます。


65
ところで、「変数テストへの割り当ては効果がありません」という警告が表示されます
AvrDragon

12
@AvrDragonただし、警告も無視する場合があります。そのため、finalキーワードを使用して取得するコンパイルエラーのように、それ以上進めないようなものを用意しておくことをお勧めします。
Sumit Desai 2013

10
@AvrDragonこれは開発環境に依存します。悪い習慣を身につけたくない限り、IDEに頼ってこのようなものをとらえてはいけません。
b1nary.atr0phy 2013年

25
@ b1naryatr0phyは、実際にはIDEのヒントだけでなく、コンパイラの警告です
AvrDragon '28年

8
@SumitDesai「しかし、警告も無視する可能性があります。そのため、最後のキーワードを使用して取得するコンパイルエラーのように、それ以上先に進めないようなものを用意することをお勧めします。」私はあなたの主張を受け入れますが、これは多くのJava開発者が反対するであろう非常に強力な声明です。コンパイラの警告は理由があるので、有能な開発者は、その影響を考慮するように強制するためのエラーを必要としません。
Stuart Rossiter 2015

127

はい、匿名クラス、読みやすさ、意図の宣言を除いて、ほとんど価値がありません。これら3つのことは価値がありませんか?

個人的にはfinal、匿名の内部クラスで変数を使用していない限り、ローカル変数とパラメーターには使用しない傾向がありますが、パラメーター値自体が変更されないことを明確にしたい人のポイントは確かにわかります(それが参照するオブジェクトがその内容を変更する場合)。それが読みやすさを増すと思う人にとって、私はそれを行うのは完全に合理的なことだと思います。

誰かが実際にデータを一定の方法で維持ないと主張している場合、あなたの主張はより重要になります。しかし、そのような主張を見たことを思い出せません。finalそれが実際よりも効果があることを示唆する開発者のかなりの団体がいることを示唆していますか?

編集:私は本当にこれらすべてをMonty Pythonリファレンスで要約するべきでした; この質問は、「ローマ人は私たちのためにこれまで何をしたのですか?」


17
しかし、Krustyをデンマーク語で言い換えると、彼らは最近私たちのために何をしましたか?=)
James Schek、2009

ユヴァル。それは面白い!刀剣で強制しても平和は起こりそうですね!
ゴンゾブレーン

1
質問は「何を、尋ねるに類似思わなかったローマ人が私たちのために行われていますか」、それはより多くの最後のキーワードは何の批判のだからしないでください。
NobleUplift 2013年

「finalが実際よりも効果があることを示唆する多数の開発者がいることを示唆していますか?」私にとってはそれ主な問題です。それを使用している開発者のかなりの割合が、呼び出し側の渡された項目不変性を強制すると考えているのではないかと思います。もちろん、コーディング標準が概念的な誤解(「有能な」開発者が認識しなければならない)から「保護」する必要があるかどうかについての議論に引き込まれます(そして、これはSOの範囲外の意見に向かいます)タイプの質問)!
Stuart Rossiter 2015

1
@SarthakMittal:実際に使用しない限り、値はコピーされません。
Jon Skeet 2017

76

ジョンがすでに述べた、ファイナルを使用する必要ある1つのケースについて少し説明しましょう。

メソッドで匿名の内部クラスを作成し、そのクラス内でローカル変数(メソッドパラメーターなど)を使用すると、コンパイラーはパラメーターをfinalにするように強制します。

public Iterator<Integer> createIntegerIterator(final int from, final int to)
{
    return new Iterator<Integer>(){
        int index = from;
        public Integer next()
        {
            return index++;
        }
        public boolean hasNext()
        {
            return index <= to;
        }
        // remove method omitted
    };
}

ここでfromand toパラメータはfinalである必要があるため、匿名クラス内で使用できます。

この要件の理由は次のとおりです。ローカル変数はスタック上に存在するため、メソッドが実行されている間だけ存在します。ただし、匿名クラスのインスタンスはメソッドから返されるため、ずっと長く存続する可能性があります。スタックは後続のメソッド呼び出しで必要になるため、保持することはできません。

したがって、Javaが代わりに行うのは、これらのローカル変数のコピーを隠しインスタンス変数として匿名クラスに配置することです(バイトコードを調べると、それらを確認できます)。しかし、それらが最終的でない場合、匿名クラスと、他の1つが変数に加える変更を参照するメソッドを期待するかもしれません。2つのコピーではなく1つの変数しかないという幻想を維持するために、それは最終的なものでなければなりません。


1
あなたは「しかし、彼らが最終的でないなら……」から私を失いました。言い換えれば、それを言い換えてみてください。たぶん私は十分なコーヒーを持っていませんでした。
hhafez 2009

1
ローカル変数があります-問題は、メソッド内でanonクラスインスタンスを使用し、それがfromの値を変更した場合に何が起こるかです-変数は1つしか表示されないため、変更はメソッドに表示されると期待されます。この混乱を避けるために、それは最終的なものでなければなりません。
マイケルボルグワート

これはコピーを作成するのではなく、参照されたオブジェクトへの参照にすぎません。
vickirk 2010

1
@vickirk:参照タイプの場合は、参照のコピーを作成してください。
Michael Borgwardt、2010

ところで、これらの変数を参照する匿名クラスがないと仮定してfinal、HotSpotの機能パラメーターと非最終関数パラメーターの間に違いがあるかどうかをご存知ですか?
パセリエ

25

私は常にfinalパラメータを使用しています。

それだけ追加されますか?あんまり。

オフにしますか?番号。

理由:人がずさんなコードを書いて、アクセサーでメンバー変数を設定できなかったバグを3つ見つけました。すべてのバグを見つけるのは難しいことがわかりました。

これが将来のバージョンのJavaでデフォルトになることを期待しています。値渡し/参照によるものは、非常に多くの後輩プログラマーをつまずかせます。

もう1つ..私のメソッドはパラメーターの数が少ない傾向があるため、メソッド宣言の余分なテキストは問題になりません。


4
私もこれを提案しようとしていましたが、これは将来のバージョンでのデフォルトがデフォルトであり、考えられる「可変」またはより優れたキーワードを指定する必要があることを示しています。これに関する素晴らしい記事があります: lpar.ath0.com/2008/08/26/java-annoyance-final-parameters
Jeff Axelrod

久しぶりですが、見つけたバグの例を教えてください。
user949300 2017

最も投票された回答を参照してください。これは、メンバー変数が設定されておらず、代わりにパラメーターが変更されている非常に良い例です。
Fortyrunner、2017

18

メソッドパラメータでfinalを使用しても、呼び出し側の引数に何が起こるかとは関係ありません。そのメソッド内で変更されていないものとしてマークすることのみを目的としています。私はより関数型のプログラミングスタイルを採用しようとしているので、その価値を理解しています。


2
正確には、それは関数インターフェースの一部ではなく、実装のみです。java finalインターフェース/抽象メソッド宣言のパラメーターを許可している(ただし、注意が必要)とは混乱します。
Beni Cherniavsky-Paskin 2015年

8

個人的には、メソッドリストのfinalを使用しません。パラメーターリストが煩雑になるためです。Checkstyleのような方法でメソッドパラメータが変更されないようにすることをお勧めします。

ローカル変数については、可能であればいつでもfinalを使用します。個人プロジェクトのセットアップでは、Eclipseにそれを自動的に行わせます。

確かに、C / C ++ constのような強力なものが欲しいです。


IDEとツールのリファレンスがOPの投稿またはトピックに適用できるかどうかは不明です。つまり、「最終」とは、参照が変更/変換されていないことをコンパイル時にチェックすることです。さらに、そのようなことを強制するには、最終参照の子メンバーに対する保護がないという回答を参照してください。たとえば、APIを構築する場合、IDEまたはツールを使用しても、そのようなコードを使用/拡張する外部の関係者を支援することはできません。
ダレルティーグ

4

Javaは引数のコピーを渡すので、妥当性finalはかなり限られていると感じています。習慣をつけるとC ++の時代から来たと思いますconst char const *。この種のものは、開発者が本質的にf ***のように愚かであり、彼が入力するすべての文字から本当に保護される必要があると信じさせてくれると思います。控えめに言っても、私が省略したとしても、私はほとんどバグを書きfinalません(自分のメソッドとクラスを誰かにオーバーライドさせたくない場合を除きます)。たぶん私は昔ながらの開発者だと思います。


1

パラメータリストでfinalを使用することはありません。以前の回答者が言ったように、混乱を加えるだけです。また、Eclipseでは、パラメーター割り当てを設定してエラーを生成することができるので、パラメーターリストでfinalを使用するのはかなり冗長に思えます。興味深いことに、パラメーター設定でEclipse設定を有効にすると、エラーが発生してこのコードがキャッチされました(これは、実際のコードではなく、フローを覚えている方法です)。

private String getString(String A, int i, String B, String C)
{
    if (i > 0)
        A += B;

    if (i > 100)
        A += C;

    return A;
}

悪魔の擁護者を演じて、これを行うことで何が間違っているのですか?


IDEとランタイムJVMを区別できるように注意してください。変数が再割り当てされるべきではないが誤って割り当てられるべきであったコードの欠陥などのメンバー変数の強盗から保護するコードをIDEが追加しない限り、コンパイルされたバイトコードがサーバー上で実行されるとき、IDEが何をしても関係ありません。最後のキーワード。
ダレルティーグ

1

短い答え:finalほんの少しは役立ちますが...代わりにクライアント側で防御プログラミングを使用してください。

実際、の問題finalは、参照が変更されないことのみを強制し、呼び出し元に気付かれずに、参照されるオブジェクトメンバーを変更できるようにすることです。したがって、この点でのベストプラクティスは、呼び出し側での防御的プログラミングであり、悪質なAPIに悪用される恐れのあるオブジェクトの深く不変のインスタンスまたは深いコピーを作成します。


2
「finalの問題は、参照のみが変更されないことを強制することです」 -真実ではありません。Java自体がそれを防ぎます。メソッドに渡された変数は、そのメソッドによって参照を変更できません。
Madbreaks

投稿する前に調査してください... stackoverflow.com/questions/40480/...を
ダレル・ティーグ

参照への参照を変更することができないというのは本当であれば、単純に守備の-コピー、不変性のない議論、最終キーワードなどは必要ないだろう、置く
ダレルティーグ

あなたは私を誤解しているか、あなたは間違っています。オブジェクト参照をメソッドに渡し、そのメソッドがそれを再割り当てした場合、メソッドの実行が完了すると、元の参照は呼び出し元(私)に対してそのまま残ります。Javaは厳密に値渡しです。そして、あなたは私が研究をしなかったと断言するのはずうずうしくてばかげている。
Madbreaks

opがfinalを使用する理由を尋ね、反対の理由を1つだけ与えたため、反対投票。
Madbreaks

0

finalをパラメーター宣言に追加するもう1つの理由は、「抽出メソッド」リファクタリングの一部として名前を変更する必要がある変数を識別するのに役立つことです。大きなメソッドのリファクタリングを開始する前に各パラメータにfinalを追加すると、続行する前に対処する必要がある問題があるかどうかがすぐにわかります。

ただし、通常はリファクタリングの最後にそれらを不要なものとして削除します。


-1

ミシェルの投稿をフォローアップしてください。私自身も、それを説明するための別の例を作りました。お役に立てれば幸いです。

public static void main(String[] args){
    MyParam myParam = thisIsWhy(new MyObj());
    myParam.setArgNewName();

    System.out.println(myParam.showObjName());
}

public static MyParam thisIsWhy(final MyObj obj){
    MyParam myParam = new MyParam() {
        @Override
        public void setArgNewName() {
            obj.name = "afterSet";
        }

        @Override
        public String showObjName(){
            return obj.name;
        }
    };

    return myParam;
}

public static class MyObj{
    String name = "beforeSet";
    public MyObj() {
    }
}

public abstract static class MyParam{
    public abstract void setArgNewName();
    public abstract String showObjName();
}

上記のコードから、thisIsWhy()メソッドでは、実際に[引数MyObj obj]をMyParamの実際の参照割り当ていません。代わりに、MyParam内のメソッドで[引数MyObj obj]を使用します

しかし、我々は法の終了した後thisIsWhyを() 引数が(オブジェクト)このmyobjはまだ存在している必要がありますか?

mainでまだメソッドshowObjName()を呼び出しており、 objに到達する必要があるので、そうであるように見えます。MyParamは、メソッドがすでに返されていても、メソッド引数を使用/到達します!

Javaが実際にこれを実現する方法は、コピーを生成することでもありますが、MyParamオブジェクト内の引数MyObj objの非表示の参照です(ただし、MyParamの正式なフィールドではないため、見えません)。

「showObjName」を呼び出すと、その参照を使用して対応する値が取得されます。

ただし、引数をfinalに設定しなかった場合は、新しいメモリ(オブジェクト)を引数MyObj objに再度割り当てることができます。

技術的にはまったく衝突はありません!これが許可されている場合、以下の状況になります。

  1. これで、MyParamオブジェクトにある[ヒープ内のメモリA]を指す[MyObj obj]ポイントが非表示になりました。
  2. また、thisIsWhyメソッドに[ヒープ内のメモリB]への引数ポイントである別の[MyObj obj]が追加されました。

衝突はありませんが、「混乱しています!!」それらはすべて"obj"である同じ "参照名"を使用しているためです。

これを回避するには、プログラマが「間違いやすい」コードを実行しないように、「最終」に設定します。

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