427

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

12 に答える 12

302

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

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

変数が別のオブジェクトに再割り当てされるのをコンパイラに防止させたい場合は、キーワード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 はすべてのパラメーター/引数変数を「最終」として自動的に強制するように設計されている必要があります。つまり、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;
    }
}
于 2012-04-30T08:20:56.447 に答える
237

変数が変更されないことを (読みやすくするために) 明示的にするとよい場合があります。これは簡単な例finalです。

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

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

于 2009-02-02T05:18:28.847 に答える
129

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

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

誰かが実際にそうではない方法でデータを一定に保っていると主張している場合、あなたの主張はより重要になりますがそのような主張を見たのを覚えていません。final実際よりも多くの効果があることを示唆している開発者が大勢いることを示唆していますか?

編集:モンティ・パイソンのリファレンスでこれらすべてを要約する必要がありました。この質問は、「ローマ人は私たちのために何をしてくれましたか?」という質問に似ているように思えます。

于 2009-02-01T09:54:45.683 に答える
77

Jon が既に述べた final を使用する必要ある 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
    };
}

ここで、fromおよびtoパラメータは、匿名クラス内で使用できるように final である必要があります。

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

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

于 2009-02-01T11:23:51.980 に答える
20

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

于 2009-02-01T10:13:55.303 に答える
8

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

ローカル変数については、可能な限り final を使用します。個人的なプロジェクトのセットアップでは、Eclipse に自動的に実行させます。

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

于 2009-02-01T11:01:50.137 に答える
5

finalJava は引数のコピーを渡すため、関連性はかなり制限されていると感じます。この習慣は、C++ の時代から来ていると思いますconst char const *。. この種のことは、開発者が本質的に愚かであり、彼が入力するすべての文字から保護する必要があると思わせると思います. final謙虚に申し上げると、省略しても(誰かにメソッドやクラスをオーバーライドさせたくない場合を除いて)バグはほとんど書いていません。多分私はただの古い学校の開発者です。

于 2013-10-29T13:18:22.197 に答える
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;
}

悪魔の擁護者を演じて、これを行うことの何が問題なのですか?

于 2013-01-07T16:08:33.677 に答える
0

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

ただし、通常は、リファクタリングの最後に余分なものとして削除します。

于 2009-02-02T05:42:40.373 に答える
-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()では、実際には[argument MyObj obj]を MyParam の実際の参照に割り当てていません。代わりに、 MyParam内のメソッドで[argument MyObj obj]を使用するだけです。

しかし、メソッドthisIsWhy()を終了した後、引数 (オブジェクト) MyObj はまだ存在する必要がありますか?

main でまだメソッド showObjName()を呼び出しており、 objに到達する必要があることがわかります。MyParam は、メソッドが既に返されていても、引き続きメソッド引数を使用/到達します!

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

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

しかし、引数を final にしないと、新しいメモリ (オブジェクト) を引数 MyObj objに再割り当てできます。

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

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

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

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

于 2016-03-17T18:33:39.823 に答える