私はJava 8でラムダで遊んでいて、警告に出くわしましたlocal variables referenced from a lambda expression must be final or effectively final
。匿名クラス内で変数を使用する場合、それらは外部クラスでfinalでなければならないことは知っていますが、それでも-finalと実質的にfinalの違いは何ですか?
私はJava 8でラムダで遊んでいて、警告に出くわしましたlocal variables referenced from a lambda expression must be final or effectively final
。匿名クラス内で変数を使用する場合、それらは外部クラスで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は事実上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
numberLength
がこのメソッドのローカル変数になるようにします。
「実質的に最終的な」ことを説明する最も簡単な方法はfinal
、変数宣言に修飾子を追加することを想像することです。この変更により、プログラムがコンパイル時と実行時の両方で同じように動作し続ける場合、その変数は事実上最終です。
case k
必要定数式とすることができる一定の変数(「定数変数は、プリミティブ型、または定数式で初期化されるString型の最終的な変数である」JLS 4.12.4最終の特別な場合です)変数。
ドキュメントによると:
初期化後に値が変更されない変数またはパラメーターは、事実上最終です。
基本的に、コンパイラーが変数がその初期化以外の割り当てに現れないことを検出した場合、その変数は事実上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」は、フィールドにはまったく適用されません。
bar
はここではパラメータであり、フィールドではありません。
「実効的に最終」は、「最終」によって追加された場合にコンパイラエラーを発生させない変数です。
「ブライアン・ゲッツ」の記事から、
非公式には、ローカル変数の初期値が変更されない場合、ローカル変数は事実上最終です。つまり、ローカル変数を最終として宣言しても、コンパイルは失敗しません。
以下のこの変数はfinalなので、いったん初期化されるとその値を変更することはできません。実行しようとすると、コンパイルエラーが発生します...
final int variable = 123;
しかし、このような変数を作成すると、その値を変更できます...
int variable = 123;
variable = 456;
ただし、Java 8では、すべての変数がデフォルトでfinalです。ただし、コードの2行目が存在するため、最終ではありません。したがって、上記のコードから2行目を削除すると、変数は「実質的に最終」になります...
int variable = 123;
したがって、一度だけ割り当てられる変数はすべて「実質的に最終」です。
ラムダ式が囲みスペースから割り当てられたローカル変数を使用する場合、重要な制限があります。ラムダ式は、値が変化しないローカル変数のみを使用できます。その制限は「変数キャプチャ」と呼ばれ、次のように記述されます。ラムダ式は変数ではなく値をキャプチャします。
ラムダ式が使用する可能性のあるローカル変数は、「実質的に最終的な」と呼ばれます。
実質的に最終的な変数とは、最初に割り当てられた後で値が変化しない変数です。そのような変数を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;
};
}
}
効果的な最終トピックはJLS 4.12.4で説明されており、最後の段落は明確な説明で構成されています。
変数が事実上最終である場合、その宣言にfinal修飾子を追加しても、コンパイル時エラーは発生しません。逆に、有効なプログラムでfinalと宣言されているローカル変数またはパラメーターは、final修飾子が削除されると、実質的にfinalになります。
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
}
}
}
}
final
コンパイルエラーを導入せずにキーワードを宣言に追加できない場合、それは事実上finalではありません。それはこのステートメントの反対です:「変数が事実上最終的な場合、その宣言に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
ローカル変数に修飾子を追加できれば、それは 事実上最終的なものでした。
ラムダ式はアクセスできます
静的変数、
インスタンス変数、
効果的に最終的なメソッドパラメータ、および
実質的に最終的なローカル変数。
出典:OCP:Oracle Certified Professional Java SE 8 Programmer II Study Guide、Jeanne Boyarsky、Scott Selikoff
さらに、
effectively final
変数は、値が変更されることはありませんが、それはして宣言されていない変数であるfinal
キーワード。
出典:Javaの使用開始:制御構造からオブジェクトまで(第6版)、Tony Gaddis
さらに、final
初めて使用する前に1回だけ初期化されるという意味を忘れないでください。
変数を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
。したがって、このコードには到達できません(この回答の詳細)。同じ理由で、行のコード9
も欠落しています。確かですi
ので状態を評価する必要はありませんtrue
。
一方、変数j
は事実上最終的なものですが、同じ方法では処理されません。適用されるそのような最適化はありません。の状態j
は2回評価されます。バイトコードに関係なく同じでj
ある効果的に最終。
ただし、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
}
);
final
の変数にアクセスすることができますが、Java 8でもあるもの効果的に最終的。