为什么在匿名类中只能访问最终的variables?
-
a
只能在这里决赛。 为什么? 如何在onClick()
方法中重新分配a
方法而不将其保留为私有成员?private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; } }); }
-
点击时如何返回
5 * a
? 我的意思是,private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; return b; // but return type is void } }); }
正如评论中指出的那样,在Java 8中,其中一些变得无关紧要, final
可能是隐含的。 尽pipe只有一个有效的 finalvariables可以用在匿名的内部类或lambdaexpression式中。
这基本上是由于Javapipe理闭包的方式。
当您创build匿名内部类的实例时,该类中使用的任何variables都将通过自动生成的构造函数复制其值 。 这避免了编译器必须自动生成各种额外的types来保存“局部variables”的逻辑状态,例如C#编译器…(当C#在匿名函数中捕获variables时,它确实捕获variables – 闭包可以以方法的主体所见的方式更新variables,反之亦然。)
由于该值已被复制到匿名内部类的实例中,因此如果该variables可能被该方法的其余部分修改,则看起来很奇怪 – 您可能会看到代码似乎与过时的variables一起工作因为那将会发生什么……你将会在不同的时间使用一个副本)。 同样,如果您可以在匿名内部类中进行更改,开发人员可能会希望这些更改在封闭方法的主体中可见。
使variables最终消除所有这些可能性 – 因为值不能被改变,所以不需要担心这样的改变是否可见。 允许方法和匿名内部类看到彼此的变化的唯一方法是使用可变types的一些描述。 这可能是封闭类本身,一个数组,一个可变的包装types…类似的东西。 基本上,这有点像一个方法和另一个方法之间的通信:对一个方法的参数所做的更改不会被其调用者看到,而是会看到参数引用的对象所做的更改。
如果您对Java和C#closures之间的更加详细的比较感兴趣,我有一篇文章进一步深入。 我想在这个答案中专注于Java方面:)
有一个技巧,允许匿名类更新外部范围内的数据。
private void f(Button b, final int a) { final int[] res = new int[1]; b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { res[0] = a * 5; } }); // But at this point handler is most likely not executed yet! // How should we now res[0] is ready? }
但是,由于同步问题,这个技巧并不是很好。 如果稍后调用处理程序,则需要1)如果处理程序是从不同线程调用的,则需要同步对res的访问2)需要某种标志或指示res已更新
但是,如果匿名类立即在同一个线程中被调用,这个技巧就可以正常工作。 喜欢:
// ... final int[] res = new int[1]; Runnable r = new Runnable() { public void run() { res[0] = 123; } }; r.run(); System.out.println(res[0]); // ...
匿名类是内部类 ,严格规则适用于内部类 (JLS 8.1.3) :
任何局部variables,forms方法参数或exception处理程序参数在内部类中使用但没有声明,都必须声明为final 。 任何使用但未在内部类中声明的局部variables必须明确地分配在内部类的主体之前 。
我还没有findjls或jvms的原因或解释,但我们知道,编译器为每个内部类创build一个单独的类文件,并且必须确保在这个类文件上声明的方法(在字节代码级)至less可以访问局部variables的值。
( 乔恩有完整的答案 – 我保留这个没有删除,因为有人可能对JLS规则感兴趣)
那么,在Java中,variables可以是最终的,不仅仅是一个参数,而是一个类级别的字段
public class Test { public final int a = 3;
或者像局部variables一样
public static void main(String[] args) { final int a = 3;
如果你想访问和修改一个匿名类的variables,你可能想把variables变成封闭类中的类级variables。
public class Test { public int a; public void doSomething() { Runnable runnable = new Runnable() { public void run() { System.out.println(a); a = a+1; } }; } }
你不能有一个最终的variables, 并给它一个新的价值。 final
意味着:价值是不变的,最终的。
而且由于它是最终的,Java可以安全地将其复制到本地的匿名类。 你没有得到一些对 int的引用 (尤其是因为你不能引用像Java这样的inttypes的基元,只是引用了Object )。
它只是将a的值复制到匿名类中的一个隐式int中。
您可以创build一个类级别的variables来获取返回的值。 我的意思是
class A { int k = 0; private void f(Button b, int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { k = a * 5; } }); }
现在你可以得到K的价值,并在你想要的地方使用它。
你为什么回答:
本地内部类实例绑定到Main类,并且可以访问其包含方法的最终局部variables。 当实例使用其包含方法的最后一个局部variables时,即使variables超出了范围(这实际上是Java的原始的,限制版本的闭包),variables仍保留在创build实例时保留的值。
由于本地内部类既不是类或包的成员,也不会声明具有访问级别。 (不过要清楚,自己的成员像普通class级一样有访问权限。)
访问被限制在本地最终variables的原因是,如果所有的局部variables都可以被访问,那么他们首先需要被复制到一个单独的部分,内部类可以访问它们并且维护多个副本可变局部variables可能导致数据不一致。 而最终的variables是不变的,因此任何数量的副本都不会对数据的一致性产生任何影响。
在产生它的线程终止之后,在一个内部类中的方法可能会被调用。 在你的例子中,内部类将在事件派发线程上调用,而不是在创build它的同一个线程中调用。 因此,variables的范围将会不同。 所以为了保护这样的variables赋值范围问题,你必须声明它们是最终的。
当一个匿名内部类被定义在一个方法体内时,所有在该方法范围内声明为final的variables都可以从内部类中访问。 对于标量值,一旦赋值,最终variables的值不能改变。 对于对象值,引用不能改变。 这允许Java编译器在运行时“捕获”variables的值,并将副本作为字段存储在内部类中。 一旦外部方法终止并且其堆栈框架被移除,原来的variables就消失了,但是内部类的私有拷贝仍然存在于类的内存中。
private void f(Button b, final int a[]) { b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { a[0] = a[0] * 5; } }); }
由于Jon有实现细节,所以其他可能的答案是JVM不想处理已经结束激活的logging。
考虑一下你的lambdas而不是被应用的用例存储在某个地方然后运行。
我记得在Smalltalk中,当你做这样的修改时,你会得到一个非法的商店。
也许这个把戏给你一个想法
Boolean var= new anonymousClass(){ private String myVar; //String for example @Overriden public Boolean method(int i){ //use myVar and i } public String setVar(String var){myVar=var; return this;} //Returns self instane }.setVar("Hello").method(3);