Java Lambdas:局部变量需要final,实例变量不需要
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/25055392/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Lambdas: local variables need final, instance variables don't
提问by Gerard
In a lambda, local variables need to be final, but instance variables don't. Why so?
在 lambda 中,局部变量需要是最终的,但实例变量不需要。为什么这样?
采纳答案by Adam Adamaszek
The fundamental difference between a field and a local variable is that the local variable is copiedwhen JVM creates a lambda instance. On the other hand, fields can be changed freely, because the changes to them are propagated to the outside class instance as well (their scopeis the whole outside class, as Boris pointed out below).
字段和局部变量的根本区别在于,局部变量在 JVM 创建 lambda 实例时被复制。另一方面,字段可以自由更改,因为对它们的更改也会传播到外部类实例(它们的范围是整个外部类,正如鲍里斯在下面指出的那样)。
The easiest way of thinking about anonymous classes, closures and labmdas is from the variable scopeperspective; imagine a copy constructor added for all local variables you pass to a closure.
考虑匿名类、闭包和 labmda 的最简单方法是从变量作用域的角度;想象为传递给闭包的所有局部变量添加了一个复制构造函数。
回答by Boris the Spider
It seems like you are asking about variables that you can reference from a lambda body.
您似乎在询问可以从 lambda 体中引用的变量。
From the JLS §15.27.2
Any local variable, formal parameter, or exception parameter used but not declared in a lambda expression must either be declared final or be effectively final (§4.12.4), or a compile-time error occurs where the use is attempted.
任何使用但未在 lambda 表达式中声明的局部变量、形式参数或异常参数必须声明为 final 或有效 final (§4.12.4),否则在尝试使用时会发生编译时错误。
So you don't need to declare variables as final
you just need to make sure that they are "effectively final". This is the same rule as applies to anonymous classes.
所以你不需要声明变量,因为final
你只需要确保它们“有效地最终”。这与适用于匿名类的规则相同。
回答by Not a bug
In document of project lambda : State of the Lambda v4
在项目 lambda 的文档中:Lambda v4 的状态
Under Section 7. Variable capture, It is mentioned that....
在第7节变量捕获下,提到......
It is our intent to prohibit capture of mutable local variables. The reason is that idioms like this:
int sum = 0; list.forEach(e -> { sum += e.size(); });
are fundamentally serial; it is quite difficult to write lambda bodies like this that do not have race conditions. Unless we are willing to enforce—preferably at compile time—that such a function cannot escape its capturing thread, this feature may well cause more trouble than it solves.
我们的意图是禁止捕获可变局部变量。原因是像这样的成语:
int sum = 0; list.forEach(e -> { sum += e.size(); });
基本上是连续的;像这样编写没有竞争条件的 lambda 体是相当困难的。除非我们愿意——最好是在编译时——强制这样一个函数不能逃脱它的捕获线程,否则这个特性很可能会导致比它解决的更多的麻烦。
Edit :
编辑 :
Another thing to note here is, local variables are passed in constructor of inner class when you access them inside your inner class, and this won't work with non final variable because value of non-final variables can be changed after construction.
这里要注意的另一件事是,当您在内部类中访问局部变量时,会在内部类的构造函数中传递它们,这不适用于非最终变量,因为非最终变量的值可以在构造后更改。
While in case of instance variable, compiler passes reference of class and class' reference will be used to access instance variable. So it is not required in case of instance variables.
而在实例变量的情况下,编译器传递类的引用,类的引用将用于访问实例变量。所以在实例变量的情况下不需要它。
PS : It is worth mentioning that, anonymous classes can access only final local variables (in JAVA SE 7), while in Java SE 8 you can access effectively final variables also inside lambda as well as inner classes.
PS:值得一提的是,匿名类只能访问最终局部变量(在 JAVA SE 7 中),而在 Java SE 8 中,您也可以在 lambda 内部以及内部类中有效地访问最终变量。
回答by newacct
Because instance variables are always accessed through a field access operation on a reference to some object, i.e. some_expression.instance_variable
. Even when you don't explicitly access it through dot notation, like instance_variable
, it is implicitly treated as this.instance_variable
(or if you're in an inner class accessing an outer class's instance variable, OuterClass.this.instance_variable
, which is under the hood this.<hidden reference to outer this>.instance_variable
).
因为实例变量总是通过对某个对象的引用的字段访问操作来访问,即some_expression.instance_variable
. 即使您没有通过点符号显式访问它,例如instance_variable
,它也会被隐式处理为this.instance_variable
(或者如果您在内部类中访问外部类的实例变量OuterClass.this.instance_variable
,这是在幕后this.<hidden reference to outer this>.instance_variable
)。
Thus an instance variable is never directly accessed, and the real "variable" you're directly accessing is this
(which is "effectively final" since it is not assignable), or a variable at the beginning of some other expression.
因此,永远不会直接访问实例变量,而您直接访问的真正“变量”是this
(它是“有效最终的”,因为它不可赋值),或者某个其他表达式开头的变量。
回答by losty
Here is a code example, as I didn't expect this either, I expected to be unable to modify anything outside my lambda
这是一个代码示例,因为我也没有想到,我希望无法修改我的 lambda 之外的任何内容
public class LambdaNonFinalExample {
static boolean odd = false;
public static void main(String[] args) throws Exception {
//boolean odd = false; - If declared inside the method then I get the expected "Effectively Final" compile error
runLambda(() -> odd = true);
System.out.println("Odd=" + odd);
}
public static void runLambda(Callable c) throws Exception {
c.call();
}
}
Output: Odd=true
输出:奇数=真
回答by nasioman
Within Lambda expressions you can use effectively final variables from the surrounding scope. Effectively means that it is not mandatory to declare variable final but make sure you do not change its state within the lambda expresssion.
在 Lambda 表达式中,您可以有效地使用来自周围范围的最终变量。实际上意味着声明变量 final 不是强制性的,但要确保不要在 lambda 表达式中更改其状态。
You can also use this within closures and using "this" means the enclosing object but not the lambda itself as closures are anonymous functions and they do not have class associated with them.
您也可以在闭包中使用 this 并且使用“this”表示封闭对象而不是 lambda 本身,因为闭包是匿名函数并且它们没有与之关联的类。
So when you use any field (let say private Integer i;)from the enclosing class which is not declared final and not effectively final it will still work as the compiler makes the trick on your behalf and insert "this" (this.i).
因此,当您使用未声明为 final 且不是有效 final 的封闭类中的任何字段(比如 private Integer i;)时,它仍然可以工作,因为编译器代表您制作技巧并插入“this”(this.i) .
private Integer i = 0;
public void process(){
Consumer<Integer> c = (i)-> System.out.println(++this.i);
c.accept(i);
}
回答by sedooe
In Java 8 in Actionbook, this situation is explained as:
在Java 8 in Action一书中,这种情况被解释为:
You may be asking yourself why local variables have these restrictions. First, there's a key difference in how instance and local variables are implemented behind the scenes. Instance variables are stored on the heap, whereas local variables live on the stack. If a lambda could access the local variable directly and the lambda were used in a thread, then the thread using the lambda could try to access the variable after the thread that allocated the variable had deallocated it. Hence, Java implements access to a free local variable as access to a copy of it rather than access to the original variable. This makes no difference if the local variable is assigned to only once—hence the restriction. Second, this restriction also discourages typical imperative programming patterns (which, as we explain in later chapters, prevent easy parallelization) that mutate an outer variable.
您可能会问自己为什么局部变量有这些限制。首先,实例变量和局部变量在幕后的实现方式有一个关键的区别。实例变量存放在堆上,而局部变量存放在栈上。如果 lambda 可以直接访问局部变量并且在线程中使用了 lambda,那么使用 lambda 的线程可以在分配变量的线程释放变量后尝试访问该变量。因此,Java 将访问自由局部变量实现为访问它的副本,而不是访问原始变量。如果局部变量只分配一次,这没有区别——因此有限制。其次,这种限制也不鼓励典型的命令式编程模式(正如我们在后面的章节中解释的那样,
回答by Hearen
YES, you can change the member variablesof the instance but you CANNOTchange the instance itself just like when you handle variables.
是的,您可以更改实例的成员变量,但不能像处理变量一样更改实例本身。
Something like this as mentioned:
像这样提到的:
class Car {
public String name;
}
public void testLocal() {
int theLocal = 6;
Car bmw = new Car();
bmw.name = "BMW";
Stream.iterate(0, i -> i + 2).limit(2)
.forEach(i -> {
// bmw = new Car(); // LINE - 1;
bmw.name = "BMW NEW"; // LINE - 2;
System.out.println("Testing local variables: " + (theLocal + i));
});
// have to comment this to ensure it's `effectively final`;
// theLocal = 2;
}
The basic principle to restrict the local variablesis about data and computation validity
限制局部变量的基本原则是关于数据和计算的有效性
If the lambda, evaluated by the second thread, were given the ability to mutate local variables. Even the ability to read the value of mutable local variables from a different thread would introduce the necessity for synchronizationor the use of volatilein order to avoid reading stale data.
如果由第二个线程评估的 lambda 能够改变局部变量。即使能够从不同的线程读取可变局部变量的值也会引入同步的必要性或使用volatile以避免读取过时的数据。
But as we know the principal purposeof the lambdas
Amongst the different reasons for this, the most pressing one for the Java platform is that they make it easier to distribute processing of collections over multiple threads.
在造成这种情况的不同原因中,Java 平台最紧迫的一个原因是它们可以更轻松地在多个线程上分发集合处理。
Quite unlike local variables, local instancecan be mutated, because it's sharedglobally. We can understand this better via the heap and stack difference:
与局部变量完全不同的是,局部实例可以改变,因为它是全局共享的。我们可以通过堆和栈的区别更好地理解这一点:
Whenever an object is created, it's always stored in the Heap space and stack memory contains the reference to it. Stack memory only contains local primitive variables and reference variables to objects in heap space.
每当创建一个对象时,它总是存储在堆空间中,堆栈内存包含对它的引用。堆栈内存只包含局部原始变量和堆空间中对象的引用变量。
So to sum up, there are two points I think really matter:
所以总结一下,我认为有两点很重要:
It's really hard to make the instanceeffectively final, which might cause lots of senseless burden (just imagine the deep-nested class);
the instance itself is already globally shared and lambda is also shareable among threads, so they can work together properly since we know we're handling the mutationand want to pass this mutation around;
很难让实例有效地成为 final,这可能会导致很多毫无意义的负担(想象一下深层嵌套的类);
实例本身已经是全局共享的,并且 lambda 也可以在线程之间共享,因此它们可以正常工作,因为我们知道我们正在处理变异并希望传递这个变异;
Balance point here is clear: if you know what you are doing, you can do it easilybut if not then the default restrictionwill help to avoid insidiousbugs.
这里的平衡点很明确:如果你知道你在做什么,你可以很容易地做到,但如果不知道,那么默认限制将有助于避免潜在的错误。
P.S. If the synchronizationrequired in instance mutation, you can use directly the stream reduction methodsor if there is dependency issue in instance mutation, you still can use thenApply
or thenCompose
in Functionwhile mapping
or methods similar.
PS 如果实例变异需要同步,可以直接使用流缩减方法,或者如果实例变异存在依赖问题,你仍然可以使用或在Functionwhile或类似的方法。thenApply
thenCompose
mapping
回答by hagrawal
Putting up some concepts for future visitors:
为未来的访客提出一些概念:
Basically it all boils down to the point that compiler should be able to deterministically tell that lambda expression body is not working on a stale copy of the variables.
基本上这一切都归结为编译器应该能够确定地告诉 lambda 表达式主体不在变量的陈旧副本上工作。
In case of local variables, compiler has no way to be sure that lambda expression body is not working on a stale copy of the variable unless that variable is final or effectively final, so local variables should be either final or effectively final.
在局部变量的情况下,编译器无法确保 lambda 表达式主体不在变量的陈旧副本上工作,除非该变量是最终的或有效的最终,因此局部变量应该是最终的或有效的最终。
Now, in case of instance fields, when you access an instance field inside the lambda expression then compiler will append a this
to that variable access (if you have not done it explicitly) and since this
is effectively final so compiler is sure that lambda expression body will always have the latest copy of the variable (please note that multi-threading is out of scope right now for this discussion). So, in case instance fields, compiler can tell that lambda body has latest copy of instance variable so instance variables need not to be final or effectively final. Please refer below screen shot from an Oracle slide:
现在,在实例字段的情况下,当您访问 lambda 表达式中的实例字段时,编译器会将 a 附加this
到该变量访问(如果您没有明确完成)并且因为this
实际上是最终的,所以编译器确保 lambda 表达式主体将始终拥有变量的最新副本(请注意,多线程现在超出了本讨论的范围)。因此,在实例字段的情况下,编译器可以判断 lambda 主体具有实例变量的最新副本,因此实例变量不需要是最终的或实际上是最终的。请参考以下 Oracle 幻灯片的屏幕截图:
Also, please note that if you are accessing an instance field in lambda expression and that is getting executed in multi-threaded environment then you could potentially run in problem.
另外,请注意,如果您正在访问 lambda 表达式中的实例字段并且该字段在多线程环境中执行,那么您可能会遇到问题。
回答by Sambhav
First, there is a key difference in how local and instance variables are implemented behind the scenes. Instance variables are stored in the heap, whereas local variables stored in the stack. If the lambda could access the local variable directly and the lambda was used in a thread, then the thread using the lambda could try to access the variable after the thread that allocated the variable had deallocated it.
首先,局部变量和实例变量在幕后的实现方式有一个关键的区别。实例变量存储在堆中,而局部变量存储在堆栈中。如果 lambda 可以直接访问局部变量并且在线程中使用了 lambda,那么使用 lambda 的线程可以在分配变量的线程释放变量后尝试访问该变量。
In short: to ensure another thread does not override the original value, it is better to provide access to the copy variable rather than the original one.
简而言之:为了确保另一个线程不会覆盖原始值,最好提供对复制变量而不是原始变量的访问。