最終版と効果的な最終版の違い


350

私はJava 8でラムダで遊んでいて、警告に出くわしましたlocal variables referenced from a lambda expression must be final or effectively final。匿名クラス内で変数を使用する場合、それらは外部クラスでfinalでなければならないことは知っていますが、それでも-final実質的にfinalの違いは何ですか?


2
答えはたくさんありますが、本質的にはすべて「違いはありません」。しかし、それは本当ですか?残念ながら、私は、Java 8の言語仕様を見つけることができないよう
アレクサンドル・ダビンスキー氏

3
@AleksandrDubinsky docs.oracle.com/javase/specs
eis

@AleksandrDubinskyは「本当に」真実ではありません。このルールの例外が1つ見つかりました。定数で初期化されたローカル変数は、コンパイラにとって定数式ではありません。最後のキーワードを明示的に追加するまで、スイッチ/ケースのケースにそのような変数を使用することはできません。たとえば、「int k = 1; switch(someInt){case k:...」です。
Henno Vermeulen 2015

回答:


233

... Java SE 8以降では、ローカルクラスは、ローカル変数と、最終または実質的に最終である囲みブロックのパラメーターにアクセスできます。初期化後に値が変更されない変数またはパラメーターは、事実上最終です。

たとえば、変数numberLengthがfinalとして宣言されておらず、マークされた代入ステートメントをPhoneNumberコンストラクターに追加するとします。

public class OutterClass {  

  int numberLength; // <== not *final*

  class PhoneNumber {

    PhoneNumber(String phoneNumber) {
        numberLength = 7;   // <== assignment to numberLength
        String currentNumber = phoneNumber.replaceAll(
            regularExpression, "");
        if (currentNumber.length() == numberLength)
            formattedPhoneNumber = currentNumber;
        else
            formattedPhoneNumber = null;
     }

  ...

  }

...

}

この割り当てステートメントのため、変数numberLengthは事実上finalにはなりません。その結果、Javaコンパイラーは、「内部クラスから参照されるローカル変数はfinalまたは実質的にfinalでなければならない」のようなエラーメッセージを生成し、内部クラスPhoneNumberがnumberLength変数にアクセスしようとします。

http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html

http://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html


68
+1注:参照が変更されない場合、参照されるオブジェクトが変更されても、事実上最終的なものになります。
Peter Lawrey、2014年

1
:@stanleyerrorこれは、いくつかの助けになるかもしれませんstackoverflow.com/questions/4732544/...

1
最終的に効果的でない例よりも、何か効果的に最終的である場合の例が役立つと思います。説明はそれを明確にしますが。コードが値を変更しない場合、Varをfinalと宣言する必要はありません。
Skychan

1
例は正しくありません。このコードは完全にコンパイルされます(もちろんドットなし)。コンパイラエラーを取得するには、このコードをメソッドの内部に配置して、それnumberLengthがこのメソッドのローカル変数になるようにします。
mykola

1
この例が非常に複雑である理由はありますか?なぜコードの大部分が完全に無関係な正規表現操作を扱っているのですか?そして、@ mykolaがすでに言ったように、有効な最終プロパティに関するマークは完全に欠落しています。これは、ローカル変数にのみ関連し、この例ではローカル変数がないためです。
Holger

131

「実質的に最終的な」ことを説明する最も簡単な方法はfinal、変数宣言に修飾子を追加することを想像することです。この変更により、プログラムがコンパイル時と実行時の両方で同じように動作し続ける場合、その変数は事実上最終です。


4
これは、Java 8の「最終」の理解が十分に理解されている限り当てはまります。それ以外の場合、後で割り当てを行ったfinalと宣言されていない変数を見て、誤ってfinalでないと思います。「もちろん」と言うかもしれませんが、最新の言語バージョンの変更に全員が注意を払っているわけではありません。
fool4jesus

8
この規則の1つの例外は、定数で初期化されたローカル変数は、コンパイラにとって定数式ではないことです。最後のキーワードを明示的に追加するまで、スイッチ/ケースのケースにそのような変数を使用することはできません。たとえば、「int k = 1; switch(someInt){case k:...」です。
Henno Vermeulen 2015

2
@HennoVermeulen switch-caseは、この回答の規則の例外ではありません。言語指定case k必要定数式とすることができる一定の変数(「定数変数は、プリミティブ型、または定数式で初期化されるString型の最終的な変数である」JLS 4.12.4最終の特別な場合です)変数。
コリンDベネット

3
私の例では、コンパイラーはkが定数式ではないため、スイッチに使用できないことを報告します。finalを追加すると、定数の変数になり、スイッチで使用できるため、コンパイルの動作が変わります。だからあなたは正しい:ルールはまだ正しいです。これは単にこの例には適用されず、kが事実上最終であるかどうかについては述べていません。
Henno Vermeulen

36

ドキュメントによると:

初期化後に値が変更されない変数またはパラメーターは、事実上最終です。

基本的に、コンパイラーが変数がその初期化以外の割り当てに現れないことを検出した場合、その変数は事実上finalと見なされます。

たとえば、いくつかのクラスを考えてみましょう:

public class Foo {

    public void baz(int bar) {
        // While the next line is commented, bar is effectively final
        // and while it is uncommented, the assignment means it is not
        // effectively final.

        // bar = 2;
    }
}

ドキュメントはローカル変数について話します。barあなたの例では、ローカル変数ではなく、フィールドです。上記のエラーメッセージの「Effectively final」は、フィールドにはまったく適用されません。
Antti Haapala、2015年

6
@AnttiHaapala barはここではパラメータであり、フィールドではありません。
peter.petrov 2016

30

「実効的に最終」は、「最終」によって追加された場合にコンパイラエラーを発生させない変数です。

「ブライアン・ゲッツ」の記事から、

非公式には、ローカル変数の初期値が変更されない場合、ローカル変数は事実上最終です。つまり、ローカル変数を最終として宣言しても、コンパイルは失敗しません。

ラムダステートファイナル-ブライアンゲッツ


2
この答えはわからない単語のために、しかしブライアンの記事には、このような正確なテキストはありません、引用符として示されている追加します。これは代わりに引用です:非公式には、ローカル変数は、その初期値が変更されない場合、事実上最終的なものです。つまり、最終的に宣言しても、コンパイルエラーは発生しません。
lcfd

記事の逐語的コピーから:非公式には、ローカル変数は、その初期値が変更されない場合、事実上最終的なものです。つまり、最終的に宣言しても、コンパイルエラーは発生しません。
Ajeet Ganga

26

以下のこの変数はfinalなので、いったん初期化されるとその値を変更することはできません。実行しようとすると、コンパイルエラーが発生します...

final int variable = 123;

しかし、このような変数を作成すると、その値を変更できます...

int variable = 123;
variable = 456;

ただし、Java 8では、すべての変数がデフォルトでfinalです。ただし、コードの2行目が存在するため、最終ではありません。したがって、上記のコードから2行目を削除すると、変数は「実質的に最終」になります...

int variable = 123;

したがって、一度だけ割り当てられる変数はすべて「実質的に最終」です。


答えは簡単です。
superigno

@Eurig、「すべての変数はデフォルトで最終的なもの」に必要な引用。
パセリエ

10

変数があり、最終的または効果的に最後の時にそれが一度初期化されますし、それは決してい変異していない、その所有者クラスに。ループ内部クラス初期化することはできません

最終

final int number;
number = 23;

事実上最終

int number;
number = 34;

FinalEffective Finalは似ていますが(割り当て後も値は変わりません)、有効なFinal変数はKeywordで宣言されませんfinal


7

ラムダ式が囲みスペースから割り当てられたローカル変数を使用する場合、重要な制限があります。ラムダ式は、値が変化しないローカル変数のみを使用できます。その制限は「変数キャプチャ」と呼ばれ、次のように記述されます。ラムダ式は変数ではなく値をキャプチャします
ラムダ式が使用する可能性のあるローカル変数は、「実質的に最終的な」と呼ばれます。
実質的に最終的な変数とは、最初に割り当てられた後で値が変化しない変数です。そのような変数をfinalとして明示的に宣言する必要はありませんが、そうすることはエラーにはなりません。
例で見てみましょう。値7で初期化されたローカル変数iがあり、ラムダ式では新しい値をiに割り当てることでその値を変更しようとしています。これは、コンパイルエラーになります- 「私は外側のスコープで定義されたローカル変数は、最終的または効果的に最終でなければなりません

@FunctionalInterface
interface IFuncInt {
    int func(int num1, int num2);
    public String toString();
}

public class LambdaVarDemo {

    public static void main(String[] args){             
        int i = 7;
        IFuncInt funcInt = (num1, num2) -> {
            i = num1 + num2;
            return i;
        };
    }   
}

2

効果的な最終トピックはJLS 4.12.4で説明されており、最後の段落は明確な説明で構成されています。

変数が事実上最終である場合、その宣言にfinal修飾子を追加しても、コンパイル時エラーは発生しません。逆に、有効なプログラムでfinalと宣言されているローカル変数またはパラメーターは、final修飾子が削除されると、実質的にfinalになります。


2

finalは、キーワード付きの変数宣言ですfinal。例:

final double pi = 3.14 ;

finalプログラム全体を通して残ります。

事実上final:現在一度だけ値が割り当てられている(または一度だけ更新されている)ローカル変数またはパラメーター。プログラム全体を通じて、最終的に効果的でない可能性があります。つまり、実質的に最終的な変数は、少なくとももう1つの割り当てが割り当てられた/更新された直後に、実質的に最終的なプロパティを失う可能性があります。例:

class EffectivelyFinal {

    public static void main(String[] args) {
        calculate(124,53);
    }

    public static void calculate( int operand1, int operand2){   
     int rem = 0;  //   operand1, operand2 and rem are effectively final here
     rem = operand1%2  // rem lost its effectively final property here because it gets its second assignment 
                       // operand1, operand2 are still effectively final here 
        class operators{

            void setNum(){
                operand1 =   operand2%2;  // operand1 lost its effectively final property here because it gets its second assignment
            }

            int add(){
                return rem + operand2;  // does not compile because rem is not effectively final
            }
            int multiply(){
                return rem * operand1;  // does not compile because both rem and operand1 are not effectively final
            }
        }   
   }    
}

これは、Java言語仕様によると正しくありません。「割り当て式の左側として使用される場合は常に、割り当て前に割り当てられておらず、割り当てられていません。」変数/パラメーターは、常にまたは事実上決して最終的なものではありません。より明確に言えば、finalコンパイルエラーを導入せずにキーワードを宣言に追加できない場合、それは事実上finalではありません。それはこのステートメントの反対です:「変数が事実上最終的な場合、その宣言にfinal修飾子を追加してもコンパイル時エラーは発生しません。」
AndrewF

サンプルコードのコメントは、私のコメントに記載されているすべての理由により正しくありません。「実質的に最終」は、時間の経過とともに変化する可能性がある状態ではありません。
AndrewF

@AndrewF時間が経っても変化しない場合、最後の行がコンパイルされないと思いますか?remは、計算方法の1行目で事実上最終でした。ただし、最後の行で、コンパイラーはremが事実上最終ではないと文句を言います
科学的方法

コンパイルするために一部のコードをコードブロックから削除する必要があることは正しいですが、これは実行時の動作を反映していません。コンパイル時に、変数が効果的に最終であるかどうかを決定できます。仕様に基づいて、常に効果的に最終であるか、決して効果的に最終であるとは限りません。コンパイラーは、変数がそのスコープ全体でどのように使用されているかを静的に調べることによって、それを知ることができます。プログラムの実行中にプロパティを取得または失うことはできません。用語は仕様によって明確に定義されています。他の答えをチェックして、かなりよく説明しています。
AndrewF

1
public class LambdaScopeTest {
    public int x = 0;        
    class FirstLevel {
        public int x = 1;    
        void methodInFirstLevel(int x) {

            // The following statement causes the compiler to generate
            // the error "local variables referenced from a lambda expression
            // must be final or effectively final" in statement A:
            //
            // x = 99; 

        }
    }    
}

他の人が言ったように、初期化後に値が決して変更されない変数またはパラメーターは、事実上最終です。上記のコードでx、内部クラスの値を変更するとFirstLevel、コンパイラーからエラーメッセージが表示されます。

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


1

finalローカル変数に修飾子を追加できれば、それは 事実上最終的なものでした。

ラムダ式はアクセスできます

  • 静的変数、

  • インスタンス変数、

  • 効果的に最終的なメソッドパラメータ、および

  • 実質的に最終的なローカル変数。

出典:OCP:Oracle Certified Professional Java SE 8 Programmer II Study Guide、Jeanne Boyarsky、Scott Selikoff

さらに、

effectively final変数は、値が変更されることはありませんが、それはして宣言されていない変数であるfinalキーワード。

出典:Javaの使用開始:制御構造からオブジェクトまで(第6版)、Tony Gaddis

さらに、final初めて使用する前に1回だけ初期化されるという意味を忘れないでください。


0

変数をfinal宣言するfinal、または宣言しないが、効果的に最終的に保つと、異なるバイトコードになる可能性があります(コンパイラによって異なります)。

小さな例を見てみましょう:

    public static void main(String[] args) {
        final boolean i = true;   // 6  // final by declaration
        boolean j = true;         // 7  // effectively final

        if (i) {                  // 9
            System.out.println(i);// 10
        }
        if (!i) {                 // 12
            System.out.println(i);// 13
        }
        if (j) {                  // 15
            System.out.println(j);// 16
        }
        if (!j) {                 // 18
            System.out.println(j);// 19
        }
    }

mainメソッドの対応するバイトコード(Windows 64ビット上のJava 8u161):

  public static void main(java.lang.String[]);
    Code:
       0: iconst_1
       1: istore_1
       2: iconst_1
       3: istore_2
       4: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       7: iconst_1
       8: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      11: iload_2
      12: ifeq          22
      15: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      18: iload_2
      19: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      22: iload_2
      23: ifne          33
      26: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
      29: iload_2
      30: invokevirtual #22                 // Method java/io/PrintStream.println:(Z)V
      33: return

対応する行番号テーブル:

 LineNumberTable:
   line 6: 0
   line 7: 2
   line 10: 4
   line 15: 11
   line 16: 15
   line 18: 22
   line 19: 26
   line 21: 33

私たちはラインでソースコードを見ての通り121314バイトコードには表示されません。それはその状態を変更するため、変更しないためiですtrue。したがって、このコードには到達できません(この回答の詳細)。同じ理由で、行のコード9も欠落しています。確かですiので状態を評価する必要はありませんtrue

一方、変数j事実上最終的なものですが、同じ方法では処理されません。適用されるそのような最適化はありません。の状態jは2回評価されます。バイトコードに関係なく同じでjある効果的に最終


私はこれをコンパイラーの非効率性と見なしますが、必ずしも新しいコンパイラーに当てはまるとは限りません。完全なコンパイルでは、変数が事実上最終である場合、宣言された最終とまったく同じ最適化がすべて生成されます。そのため、事実上finalは、何かをfinalと宣言するよりも自動的に遅いという概念に依存しないでください。
AndrewF

@AndrewF一般的に、あなたの言う通り、動作は変わる可能性があります。それが「コンパイラによっては異なるバイトコードになる可能性がある」と書いた理由です。最適化が欠落しているため(バイトコードが異なるため)、実行速度が遅いとは思いません。しかし、それは示されているケースの違いです。
LuCio

0

実質的に最終的な変数は、次のローカル変数です。

  1. として定義されていません final
  2. 一度だけ割り当てられます。

最終変数は次のような変数です。

  1. finalキーワードで宣言されています。

-6

ただし、Java SE 8以降では、ローカルクラスは、最終的な、または事実上最終的な囲みブロックのローカル変数とパラメーターにアクセスできます。

これはJava 8で始まったわけではありません。長い間使用してきました。このコードは(Java 8より前の)正当なものでした:

String str = ""; //<-- not accesible from anonymous classes implementation
final String strFin = ""; //<-- accesible 
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
         String ann = str; // <---- error, must be final (IDE's gives the hint);
         String ann = strFin; // <---- legal;
         String str = "legal statement on java 7,"
                +"Java 8 doesn't allow this, it thinks that I'm trying to use the str declared before the anonymous impl."; 
         //we are forced to use another name than str
    }
);

2
声明は、Java 8 <で、事実をいうだけ finalの変数にアクセスすることができますが、Java 8であるもの効果的に最終的。
Antti Haapala、2015年

私は関係なく、あなたは、Java 7またはJava 8を使用しているかどうかの、動作しないコードを参照してください
ホルガー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.