私はJava 8でラムダで遊んでいて、警告local variables referenced from a lambda expression must be final or effectively final
に出くわしました。無名クラスの内部で変数を使用する場合、それらは外部クラスで最終的でなければならないことを知っていますが、それでも - finalと事実上finalの違いは何ですか?
... 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は事実上最終的なものではなくなりました。 結果として、Javaコンパイラーは「内部クラスから参照されるローカル変数は最終的または事実上最終的でなければなりません」のようなエラーメッセージを生成しますここで、内部クラスPhoneNumberはnumberLength変数にアクセスしようとします。
http://codeinventions.blogspot.in/2014/07/difference-between-final-and.html
http://docs.Oracle.com/javase/tutorial/Java/javaOO/localclasses.html
"事実上最終的"と説明する最も簡単な方法は、変数宣言にfinal
修飾子を追加することを想像することです。この変更により、プログラムがコンパイル時と実行時の両方で同じ方法で動作し続ける場合、その変数は事実上最終的なものになります。
docs :によると
初期化後に値が変更されない変数またはパラメータは、事実上最終的なものです。
基本的に、コンパイラが変数がその初期化以外の代入に現れないことを発見した場合、その変数は 事実上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;
}
}
以下のこの変数は final なので、一度初期化した値は変更できません。しようとすると、コンパイルエラーが発生します。
final int variable = 123;
しかし、このように変数を作成すると、その値を変更できます。
int variable = 123;
variable = 456;
しかし Java 8 では、すべての変数はデフォルトで final です。しかし、コードに2行目があると、 non-final になります。したがって、上記のコードから2行目を削除すると、変数は "事実上最終"になります ...
int variable = 123;
だから.. 一度だけそして一度だけ割り当てられたどんな変数でも、「事実上最終的な」 。
ラムダ式がその囲みスペースから割り当てられたローカル変数を使用する場合、重要な制限があります。ラムダ式は、値が変化しないローカル変数のみを使用できます。その制限は " variable capture "と呼ばれます。 変数ではなく、ラムダ式が値をキャプチャする 。
ラムダ式が使用できるローカル変数は " 事実上最終 "として知られています。
事実上最後の変数とは、最初に割り当てられた後でその値が変わらないものです。そのような変数をfinalとして明示的に宣言する必要はありませんが、そうしてもエラーにはなりません。
例を見てみましょう。ローカル変数iが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;
};
}
}
変数は final または 事実上final when それは一度初期化されます そして決して - 突然変異されることはありません そのオーナークラスでは/です。そして 初期化することはできません - ループ または 内部クラス .
最後の :
final int number;
number = 23;
事実上最終 :
int number;
number = 34;
効果的な最終 トピックは JLS 4.12.4 で説明されており、最後の段落は明確な説明で構成されています。
変数が事実上最終的である場合、その宣言に最終修飾子を追加してもコンパイル時エラーは発生しません。逆に、有効なプログラム内でfinalと宣言されているローカル変数またはパラメータは、最終修飾子が削除された場合、事実上最終的になります。
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
で宣言された変数です。例:
final double pi = 3.14 ;
プログラムを通してfinal
のままです。
事実上最終 :現在一度だけ値が割り当てられている(または一度だけ更新されている)任意のローカル変数またはパラメータ。プログラム全体を通して 事実上最後 のままではいけません。そのため、 事実上final variableは、少なくとももう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
}
}
}
}
final
修飾子をローカル変数に追加することができれば、それは 実質的にfinalでした。
ラムダ式からアクセスできる
静的変数
インスタンス変数、
最終的なメソッドパラメータ
事実上最終的なローカル変数。
出典: OCP:オラクル認定プロフェッショナルJava SE 8プログラマーII学習ガイド、Jeanne Boyarsky、Scott Selikoff
さらに、
effectively final
変数は、値が変更されない変数ですが、final
キーワードで宣言されていません。
出典: Javaで始めましょう:コントロール構造からオブジェクトまで(第6版)、Tony Gaddis
さらに、初めて使用される前に1回だけ初期化されるという意味のfinal
を忘れないでください。
変数final
を宣言するか、それを宣言しないで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
12
、13
、14
の行にソースコードがあるので、バイトコードには現れません。それはi
がtrue
であり、その状態を変えないからです。したがって、このコードは到達不能です(この answer の詳細)。同じ理由で、9
行のコードも見逃しています。 i
の状態は確かにtrue
なので評価する必要はありません。
一方、変数j
は 事実上final ですが、同じようには処理されません。そのような最適化は適用されません。 j
の状態は2回評価されます。 j
が 事実上final であるかどうかにかかわらず、バイトコードは同じです。