为什么在匿名类中只能访问最终的variables?

  1. 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; } }); } 
  2. 点击时如何返回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就消失了,但是内部类的私有拷贝仍然存在于类的内存中。

http://en.wikipedia.org/wiki/Final_%28Java%29

 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);