a只能在这里定稿。为什么?如何在不将其保留为私有成员的情况下以a方法重新分配onClick()

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匿名类提供了您期望的lambda闭包,但是如果我错了,请有人纠正我...

您想达到什么目的?单击处理程序可以在“ f”完成时执行。

@Lambert如果要在onClick方法中使用a,则必须是最终的@Ivan。f()方法的行为类似于onClick()方法如何在单击时返回int

这就是我的意思-它不支持完全闭包,因为它不允许访问非最终变量。

注意:从Java 8开始,您的变量只需要有效地成为final

#1 楼

如注释中所述,其中一些在Java 8中变得无关紧要,其中final可以是隐式的。但是,只能在匿名内部类或lambda表达式中使用有效的最终变量。


这基本上是由于Java管理闭包的方式。

创建一个匿名内部类的实例,该类中使用的任何变量都将通过自动生成的构造函数复制其值。这样避免了编译器不得不自动生成各种额外的类型来保存“局部变量”的逻辑状态,例如C#编译器确实...(当C#在匿名函数中捕获变量时,它实际上捕获了变量-闭包可以通过方法主体看到的方式更新变量,反之亦然。)

由于值已复制到匿名内部类的实例中,因此看起来如果该变量可以被该方法的其余部分修改,则很奇怪-您可能拥有似乎正在使用过期变量的代码(因为这实际上将是正在发生的事情……您将使用一个副本在其他时间拍摄)。同样,如果您可以在匿名内部类中进行更改,则开发人员可能希望这些更改在封闭方法的正文中可见。

使变量final消除了所有这些可能性,因为值可以根本不需要更改,您无需担心此类更改是否可见。允许方法和匿名内部类相互查看更改的唯一方法是使用某种描述的可变类型。这可能是封闭的类本身,一个数组,一个可变的包装器类型……诸如此类。基本上,这有点像在一种方法和另一种方法之间进行通信:调用者看不到对一个方法的参数所做的更改,但可以看到对参数所引用的对象所做的更改。

如果您想对Java和C#闭包之间的更详细的比较感兴趣,请参阅我的文章。我想在此答案中将重点放在Java方面:)

评论


是的基本上,完全闭包支持可以通过移动在特殊的自动生成类中引用的所有变量来实现。

–伊凡·杜布罗夫(Ivan Dubrov)
2011年1月19日7:14



@Ivan:基本上像C#。但是,如果要使用与C#相同的功能,那么可以将来自不同作用域的变量“实例化”不同的次数,但是它具有相当的复杂性。

–乔恩·斯基特(Jon Skeet)
2011年1月19日,7:15

对于Java 7来说都是如此,请记住,在Java 8中引入了闭包,现在确实可以从其内部类访问类的非最终字段。

–Mathias Bader
2014-10-24 14:41

@MathiasBader:真的吗?我认为它基本上仍然是相同的机制,编译器现在已经足够聪明,可以推断final(但是仍然需要有效地将其定为final)。

– Thilo
16 Mar 4 '16 at 7:56

@Mathias Bader:您始终可以访问非final字段,不要将它们与局部变量相混淆,局部变量必须是final并且仍然必须是有效final,因此Java 8不会更改语义。

–霍尔格
16年6月2日在18:27

#2 楼

有一个技巧可以使匿名类在外部范围内更新数据。

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

// ...


评论


感谢您的回答。我知道所有这些,我的解决方案比这更好。我的问题是“为什么只有决赛”?

–user467871
2011年1月19日,7:10

答案是,它们是如何实现的:)

–伊凡·杜布罗夫(Ivan Dubrov)
2011年1月19日在7:12

谢谢。我自己使用了上述技巧。我不确定这是否是个好主意。如果Java不允许,则可能有充分的理由。您的回答说明我的List.forEach代码是安全的。

–RuntimeException
2015年10月30日在11:54



阅读stackoverflow.com/q/12830611/2073130,以更好地讨论“为什么只有最终语言”的基本原理。

– lcn
2015年12月8日在23:24

有几种解决方法。我的是:final int resf = res;最初,我使用数组方法,但是我发现它的语法过于繁琐。 AtomicReference可能要慢一些(分配一个对象)。

– zakmck
17年11月29日在17:37

#3 楼

匿名类是内部类,严格规则适用于内部类(JLS 8.1.3):


使用但未在其中声明的任何局部变量,形式方法参数或异常处理程序参数内部类必须声明为final。必须在内部类的主体之前明确分配在内部类中使用但未声明的任何局部变量。


我没有找到有关jls或jvms,但我们确实知道,编译器会为每个内部类创建一个单独的类文件,并且必须确保在该类文件上声明的方法(在字节码级别上)至少可以访问local的值变量。

(Jon给出了完整的答案-我将其保留为未删除状态,因为可能会对JLS规则感兴趣)

#4 楼

您可以创建一个类级变量来获取返回值。我的意思是

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类,并且可以访问其包含方法的最终局部变量。当实例使用其包含方法的最终局部变量时,即使该变量超出范围(实际上是Java的有限的封闭版本),该变量也会保留其在实例创建时所持有的值。 br />
因为本地内部类既不是类或包的成员,所以不会使用访问级别声明它。 (但是请注意,它自己的成员具有与普通班级一样的访问级别。)

评论


我提到“不保留为私人会员”

–user467871
2011年1月19日7:11

#5 楼

好吧,在Java中,变量不仅可以作为参数,而且可以作为类级字段(如

public class Test
{
 public final int a = 3;


)或作为局部变量(如

public static void main(String[] args)
{
 final int a = 3;


如果要访问和修改匿名类中的变量,则可能希望将该变量设为封闭类中的类级变量。

public class Test
{
 public int a;
 public void doSomething()
 {
  Runnable runnable =
   new Runnable()
   {
    public void run()
    {
     System.out.println(a);
     a = a+1;
    }
   };
 }
}


您不能将变量作为final并为其赋予新值。 final的意思是:该值不可更改且为最终值。

由于它是最终值,因此Java可以安全地将其复制到本地匿名类中。您没有获得对int的引用(特别是因为您无法引用Java中的int之类的基元,而只是引用对象)。

它只是将a的值复制到an中在您的匿名类中称为a的隐式int。

评论


我将“类级变量”与静态关联。也许更清楚的是,如果您使用“实例变量”代替。

–eljenso
2011年1月19日,12:23

好吧,我使用了类级别的,因为该技术可以同时使用实例变量和静态变量。

– Zach L
2011年1月19日在17:01

我们已经知道final可以访问,但是我们想知道为什么?能否请您解释一下为什么要添加更多内容?

–索拉布·奥扎(Saurabh Oza)
19年1月29日在12:44

#6 楼

之所以只将访问限制在局部最终变量上,是因为如果所有局部变量都可以访问,则首先需要将它们复制到一个单独的节中,内部类可以访问它们并维护它们的多个副本。可变的局部变量可能导致数据不一致。最终变量是不可变的,因此对它们的任何数量的复制都不会对数据的一致性产生任何影响。

评论


这不是通过支持该功能的C#语言实现的。实际上,编译器将变量从局部变量更改为实例变量,或者为这些变量创建额外的数据结构,这些数据结构的寿命可能超出外部类的范围。但是,没有“局部变量的多个副本”

– Mike76
16-8-27在13:13



Mike76我没有看过C#的实现,但是Scala做了您提到的第二件事,我想:如果将Int重新分配给闭包内部,则将该变量更改为IntRef的实例(本质上是可变的Integer包装器)。然后,将相应地重写每个变量访问。

– Adowrath
17年9月15日在7:30

#7 楼

在方法的主体中定义匿名内部类时,可以从内部类中访问在该方法范围内声明为final的所有变量。对于标量值,一旦将其赋值,最终变量的值就无法更改。对于对象值,引用不能更改。这允许Java编译器在运行时“捕获”变量的值,并将副本存储为内部类中的字段。一旦外部方法终止并删除了其堆栈框架,原始变量就消失了,但内部类的私有副本仍保留在该类自己的内存中。

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

#8 楼

若要了解此限制的原理,请考虑以下程序:

public class Program {

    interface Interface {
        public void printInteger();
    }
    static Interface interfaceInstance = null;

    static void initialize(int val) {
        class Impl implements Interface {
            @Override
            public void printInteger() {
                System.out.println(val);
            }
        }
        interfaceInstance = new Impl();
    }

    public static void main(String[] args) {
        initialize(12345);
        interfaceInstance.printInteger();
    }
}


接口实例在初始化方法返回后仍保留在内存中,但参数val不会。 JVM无法访问其范围之外的局部变量,因此Java通过将val的值复制到interfaceInstance中具有相同名称的隐式字段来使对printInteger的后续调用起作用。据说interfaceInstance已捕获本地参数的值。如果参数不是最终参数(或实际上不是最终参数),则其值可能会更改,从而与捕获的值不同步,从而可能导致不直观的行为。

#9 楼

产生异常的内部类中的方法可以在产生它的线程终止后很好地调用。在您的示例中,内部类将在事件分发线程上调用,而不是在与创建它的线程相同的线程中调用。因此,变量的范围将不同。因此,要保护此类变量赋值范围问题,必须将它们声明为final。

#10 楼

private void f(Button b, final int a[]) {

    b.addClickHandler(new ClickHandler() {

        @Override
        public void onClick(ClickEvent event) {
            a[0] = a[0] * 5;

        }
    });
}


#11 楼

由于Jon拥有实现细节的答案,另一个可能的答案是JVM不想处理已结束激活的记录。

考虑用Lambda而不是应用Lambda的用例。 ,它存储在某个地方并稍后运行。

我记得在Smalltalk中,进行此类修改后会引发非法存储。

#12 楼

尝试下面的代码,

创建数组列表并将值放入其中并返回:

private ArrayList f(Button b, final int a)
{
    final ArrayList al = new ArrayList();
    b.addClickHandler(new ClickHandler() {

         @Override
        public void onClick(ClickEvent event) {
             int b = a*5;
             al.add(b);
        }
    });
    return al;
}


评论


OP正在询问为什么需要某些东西的原因。因此,您应该指出您的代码如何解决它

– NitinSingh
18年7月18日在10:21

#13 楼

Java匿名类非常类似于Javascript闭包,但是Java以不同的方式实现。 (请检查Andersen的答案)

因此,为了不使Java Developer与那些来自Javascript背景的人可能会发生的奇怪行为混淆。我猜这就是为什么他们强迫我们使用final,这不是JVM的限制。

让我们看下面的Javascript示例:

var add = (function () {
  var counter = 0;

  var func = function () {
    console.log("counter now = " + counter);
    counter += 1; 
  };

  counter = 100; // line 1, this one need to be final in Java

  return func;

})();


add(); // this will print out 100 in Javascript but 0 in Java


在Javascript中,counter的值为100,因为从头到尾只有一个counter变量。

但是在Java中,如果没有final,它将打印出0,因为在创建内部对象时,将0值复制到内部类对象的隐藏属性。 (这里有两个整数变量,一个在局部方法中,另一个在内部类隐藏属性中)

因此内部对象创建后的任何更改(如第1行)都不会影响内部目的。因此,这会使两个不同的结果和行为(Java和Javascript之间)混淆。

我相信这就是为什么Java决定强制将其定为最终的,因此数据从一开始就“一致”结束。

#14 楼

内部类中的Java最终变量

内部类只能使用


外部类的引用
超出范围的最终局部变量引用类型(例如Object ...)
值(原始)(例如int ...)类型可以用最终引用类型包装。 IntelliJ IDEA可以帮助您将其转换为一个元素数组


编译器生成non static nestedinner class)[About]时-一个新类-创建<OuterClass>$<InnerClass>.class并将绑定参数传递给构造函数[堆栈上的局部变量]。它类似于闭包

最终变量是无法重新分配的变量。最终引用变量仍然可以通过修改状态来更改

这很可能很奇怪,因为作为程序员,您可以像这样进行编辑

//Not possible 
private void foo() {

    MyClass myClass = new MyClass(); //address 1
    int a = 5;

    Button button = new Button();

    //just as an example
    button.addClickHandler(new ClickHandler() {


        @Override
        public void onClick(ClickEvent event) {

            myClass.something(); //<- what is the address ?
            int b = a; //<- 5 or 10 ?

            //illusion that next changes are visible for Outer class
            myClass = new MyClass();
            a = 15;
        }
    });

    myClass = new MyClass(); //address 2
    int a = 10;
}


#15 楼

也许这个技巧给了你一个主意

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