简体   繁体   English

为什么在匿名类中只能访问最终变量?

[英]Why are only final variables accessible in anonymous class?

  1. a can only be final here. a在这里只能是最终的。 Why?为什么? How can I reassign a in onClick() method without keeping it as private member?我怎样才能重新分配aonClick()方法中没有保持它作为私有成员?

     private void f(Button b, final int a){ b.addClickHandler(new ClickHandler() { @Override public void onClick(ClickEvent event) { int b = a*5; } }); }
  2. How can I return the 5 * a when it clicked?单击时如何返回5 * a I mean,我的意思是,

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

As noted in comments, some of this becomes irrelevant in Java 8, where final can be implicit.正如评论中所指出的,其中一些在 Java 8 中变得无关紧要,其中final可以是隐式的。 Only an effectively final variable can be used in an anonymous inner class or lambda expression though.但是,在匿名内部类或 lambda 表达式中只能使用有效的最终变量。


It's basically due to the way Java manages closures .这主要是由于 Java 管理闭包的方式。

When you create an instance of an anonymous inner class, any variables which are used within that class have their values copied in via the autogenerated constructor.当您创建匿名内部类的实例时,该类中使用的任何变量都会通过自动生成的构造函数复制其 This avoids the compiler having to autogenerate various extra types to hold the logical state of the "local variables", as for example the C# compiler does... (When C# captures a variable in an anonymous function, it really captures the variable - the closure can update the variable in a way which is seen by the main body of the method, and vice versa.)这避免了编译器必须自动生成各种额外类型来保存“局部变量”的逻辑状态,例如 C# 编译器所做的......(当 C# 捕获匿名函数中的变量时,它确实捕获了该变量 -闭包可以以方法主体看到的方式更新变量,反之亦然。)

As the value has been copied into the instance of the anonymous inner class, it would look odd if the variable could be modified by the rest of the method - you could have code which appeared to be working with an out-of-date variable (because that's effectively what would be happening... you'd be working with a copy taken at a different time).由于该值已被复制到匿名内部类的实例中,因此如果该变量可以被该方法的其余部分修改,那将看起来很奇怪 - 您可能拥有似乎正在使用过时变量的代码(因为这实际上是发生的事情……您将使用在不同时间拍摄的副本)。 Likewise if you could make changes within the anonymous inner class, developers might expect those changes to be visible within the body of the enclosing method.同样,如果您可以在匿名内部类中进行更改,开发人员可能希望这些更改在封闭方法的主体中可见。

Making the variable final removes all these possibilities - as the value can't be changed at all, you don't need to worry about whether such changes will be visible.将变量设为 final 消除了所有这些可能性——因为该值根本无法更改,因此您无需担心此类更改是否可见。 The only ways to allow the method and the anonymous inner class see each other's changes is to use a mutable type of some description.允许方法和匿名内部类看到彼此更改的唯一方法是使用某种描述的可变类型。 This could be the enclosing class itself, an array, a mutable wrapper type... anything like that.这可能是封闭类本身,一个数组,一个可变的包装类型......任何类似的东西。 Basically it's a bit like communicating between one method and another: changes made to the parameters of one method aren't seen by its caller, but changes made to the objects referred to by the parameters are seen.基本上它有点像在一个方法和另一个方法之间进行通信:对一个方法的参数所做的更改不被其调用者看到,但对参数所引用的对象所做的更改是可见的。

If you're interested in a more detailed comparison between Java and C# closures, I have an article which goes into it further.如果您对 Java 和 C# 闭包之间更详细的比较感兴趣,我有一篇文章对其进行了进一步的讨论。 I wanted to focus on the Java side in this answer :)我想在这个答案中关注 Java 方面:)

There is a trick that allows anonymous class to update data in the outer scope.有一个技巧可以让匿名类更新外部作用域中的数据。

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?
}

However, this trick is not very good due to the synchronization issues.但是,由于同步问题,这个技巧不是很好。 If handler is invoked later, you need to 1) synchronize access to res if handler was invoked from the different thread 2) need to have some sort of flag or indication that res was updated如果稍后调用处理程序,则需要 1) 如果处理程序是从不同线程调用的,则需要同步对 res 的访问 2) 需要具有某种标志或指示 res 已更新

This trick works OK, though, if anonymous class is invoked in the same thread immediately.但是,如果立即在同一线程中调用匿名类,则此技巧可以正常工作。 Like:像:

// ...

final int[] res = new int[1];
Runnable r = new Runnable() { public void run() { res[0] = 123; } };
r.run();
System.out.println(res[0]);

// ...

An anonymous class is an inner class and the strict rule applies to inner classes (JLS 8.1.3) :匿名类是内部类,严格规则适用于内部类(JLS 8.1.3)

Any local variable, formal method parameter or exception handler parameter used but not declared in an inner class must be declared final .任何使用但未在内部类中声明的局部变量、形式方法参数或异常处理程序参数都必须声明为 final Any local variable, used but not declared in an inner class must be definitely assigned before the body of the inner class .任何在内部类中使用但未声明的局部变量必须在内部类的主体之前明确分配

I haven't found a reason or an explanation on the jls or jvms yet, but we do know, that the compiler creates a separate class file for each inner class and it has to make sure, that the methods declared on this class file (on byte code level) at least have access to the values of local variables.我还没有找到关于 jls 或 jvms 的原因或解释,但我们确实知道,编译器为每个内部类创建一个单独的类文件,它必须确保在这个类文件中声明的方法(在字节码级别)至少可以访问局部变量的值。

( Jon has the complete answer - I keep this one undeleted because one might interested in the JLS rule) 乔恩有完整的答案——我没有删除这个,因为人们可能对 JLS 规则感兴趣)

You can create a class level variable to get returned value.您可以创建一个类级别变量来获取返回值。 I mean我的意思是

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

now you can get value of K and use it where you want.现在您可以获得 K 的值并在您想要的地方使用它。

Answer of your why is :你为什么的答案是:

A local inner class instance is tied to Main class and can access the final local variables of its containing method.局部内部类实例绑定到 Main 类,并且可以访问其包含方法的最终局部变量。 When the instance uses a final local of its containing method, the variable retains the value it held at the time of the instance's creation, even if the variable has gone out of scope (this is effectively Java's crude, limited version of closures).当实例使用其包含方法的最终局部变量时,该变量会保留实例创建时它所持有的值,即使该变量已超出范围(这实际上是 Java 的原始、有限版本的闭包)。

Because a local inner class is neither the member of a class or package, it is not declared with an access level.因为局部内部类既不是类的成员,也不是包的成员,所以它没有声明访问级别。 (Be clear, however, that its own members have access levels like in a normal class.) (但是,请注意,它自己的成员具有与普通类一样的访问级别。)

The reason why the access has been restricted only to the local final variables is that if all the local variables would be made accessible then they would first required to be copied to a separate section where inner classes can have access to them and maintaining multiple copies of mutable local variables may lead to inconsistent data.访问仅限于局部最终变量的原因是,如果所有局部变量都可以访问,那么首先需要将它们复制到内部类可以访问它们的单独部分并维护多个副本可变局部变量可能会导致数据不一致。 Whereas final variables are immutable and hence any number of copies to them will not have any impact on the consistency of data.而最终变量是不可变的,因此任何数量的副本都不会对数据的一致性产生任何影响。

Well, in Java, a variable can be final not just as a parameter, but as a class-level field, like好吧,在 Java 中,变量不仅可以作为参数,而且可以作为类级别的字段,例如

public class Test
{
 public final int a = 3;

or as a local variable, like或作为局部变量,如

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

If you want to access and modify a variable from an anonymous class, you might want to make the variable a class-level variable in the enclosing class.如果要访问和修改匿名类中的变量,您可能希望将该变量设为封闭类中的类变量。

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

You can't have a variable as final and give it a new value.您不能将变量作为最终变量为其赋予新值。 final means just that: the value is unchangeable and final. final意味着:该值是不可更改且最终的。

And since it's final, Java can safely copy it to local anonymous classes.由于它是最终的,Java 可以安全地复制到本地匿名类。 You're not getting some reference to the int (especially since you can't have references to primitives like int in Java, just references to Objects ).你没有得到一些int 的引用(特别是因为你不能在 Java 中引用像 int 这样的原语,只能引用Objects )。

It just copies over the value of a into an implicit int called a in your anonymous class.它只是将 a 的值复制到匿名类中称为 a 的隐式 int 中。

To understand the rationale for this restriction, consider the following program:要了解此限制的基本原理,请考虑以下程序:

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

The interfaceInstance remains in memory after the initialize method returns, but the parameter val does not. interfaceInstanceinitialize方法返回后仍保留在内存中,但参数val不在。 The JVM can't access a local variable outside its scope, so Java makes the subsequent call to printInteger work by copying the value of val to an implicit field of the same name within interfaceInstance . JVM 无法访问其范围之外的局部变量,因此 Java 通过将val的值复制到interfaceInstance中的同名隐式字段来使对printInteger的后续调用起作用。 The interfaceInstance is said to have captured the value of the local parameter.据说interfaceInstance已经捕获了本地参数的值。 If the parameter weren't final (or effectively final) its value could change, becoming out of sync with the captured value, potentially causing unintuitive behavior.如果参数不是最终的(或实际上是最终的),它的值可能会改变,与捕获的值不同步,可能会导致不直观的行为。

When an anonymous inner class is defined within the body of a method, all variables declared final in the scope of that method are accessible from within the inner class.当在方法体中定义匿名内部类时,所有在该方法范围内声明为 final 的变量都可以从内部类中访问。 For scalar values, once it has been assigned, the value of the final variable cannot change.对于标量值,一旦被赋值,最终变量的值就不能改变。 For object values, the reference cannot change.对于对象值,引用不能更改。 This allows the Java compiler to "capture" the value of the variable at run-time and store a copy as a field in the inner class.这允许 Java 编译器在运行时“捕获”变量的值并将副本存储为内部类中的字段。 Once the outer method has terminated and its stack frame has been removed, the original variable is gone but the inner class's private copy persists in the class's own memory.一旦外部方法终止并且其堆栈帧被移除,原始变量就消失了,但内部类的私有副本仍然存在于类自己的内存中。

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

Methods within an anonomyous inner class may be invoked well after the thread that spawned it has terminated.在生成它的线程终止之后,可以很好地调用匿名内部类中的方法。 In your example, the inner class will be invoked on the event dispatch thread and not in the same thread as that which created it.在您的示例中,内部类将在事件调度线程上调用,而不是在与创建它的线程相同的线程中调用。 Hence, the scope of the variables will be different.因此,变量的范围将不同。 So to protect such variable assignment scope issues you must declare them final.因此,为了保护此类变量赋值范围问题,您必须将它们声明为 final。

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

    b.addClickHandler(new ClickHandler() {

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

        }
    });
}

As Jon has the implementation details answer an other possible answer would be that the JVM doesn't want to handle write in record that have ended his activation.由于Jon有实现细节的答案,另一个可能的答案是 JVM 不想处理已结束激活的写入记录。

Consider the use case where your lambdas instead of being apply, is stored in some place and run later.考虑这样一个用例,您的 lambda 表达式不是被应用,而是存储在某个地方并稍后运行。

I remember that in Smalltalk you would get an illegal store raised when you do such modification.我记得在 Smalltalk 中,当你做这样的修改时,你会得到一个非法存储。

Try this code,试试这个代码,

Create Array List and put value inside that and return it :创建数组列表并将值放入其中并返回它:

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

Java anonymous class is very similar to Javascript closure, but Java implement that in different way. Java 匿名类与 Javascript 闭包非常相似,但 Java 以不同的方式实现它。 (check Andersen's answer) (检查安徒生的回答)

So in order not to confuse the Java Developer with the strange behavior that might occur for those coming from Javascript background.因此,为了不让 Java 开发人员与那些来自 Javascript 背景的人可能发生的奇怪行为混淆。 I guess that's why they force us to use final , this is not the JVM limitation.我想这就是为什么他们强迫我们使用final ,这不是 JVM 限制。

Let's look at the Javascript example below:让我们看看下面的 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

In Javascript, the counter value will be 100, because there is only one counter variable from the beginning to end.在 Javascript 中, counter值将是 100,因为从头到尾只有一个counter变量。

But in Java, if there is no final , it will print out 0 , because while the inner object is being created, the 0 value is copied to the inner class object's hidden properties.但是在 Java 中,如果没有final ,它会打印出0 ,因为在创建内部对象时, 0值被复制到内部类对象的隐藏属性中。 (there are two integer variable here, one in the local method, another one in inner class hidden properties) (这里有两个整型变量,一个在本地方法中,一个在内部类隐藏属性中)

So any changes after the inner object creation (like line 1), it will not affect the inner object.所以内部对象创建后的任何更改(如第 1 行),都不会影响内部对象。 So it will make confusion between two different outcome and behaviour (between Java and Javascript).所以它会混淆两种不同的结果和行为(Java 和 Javascript 之间)。

I believe that's why, Java decide to force it to be final, so the data is 'consistent' from the beginning to end.我相信这就是为什么 Java 决定强制它成为最终的,因此数据从头到尾都是“一致的”。

Java final variable inside an inner class [About] inner class Java final变量[关于]

inner class can use only内部类只能使用

  1. reference from outer class来自外部类的引用
  2. final local variables from out of scope which are a reference type (eg Object ...)超出范围的最终局部变量是引用类型(例如Object ...)
  3. value(primitive) (eg int ...) type can be wrapped by a final reference type. value(primitive)(例如int ...)类型可以由最终引用类型包装 IntelliJ IDEA can help you covert it to one element array IntelliJ IDEA可以帮你把它转换成一个元素数组

When a non static nested ( inner class ) is generated by compiler - a new class - <OuterClass>$<InnerClass>.class is created and bounded parameters are passed into constructor [Local variable on stack] It is similar to closure [Swift about]当编译器生成一个non static nestedinner class )时 - 创建一个新类 - <OuterClass>$<InnerClass>.class并将有界参数传递给构造函数[堆栈上的局部变量]类似于闭包[Swift about ]

final variable is a variable which can not be reassign. final 变量是一个不能重新赋值的变量。 final reference variable still can be changed by modifying a state最终参考变量仍然可以通过修改状态来改变

If it was be possible it would be weird because as a programmer you could make like this如果有可能,那会很奇怪,因为作为一名程序员,你可以像这样

//Not possible 
private void foo() {

    MyClass myClass = new MyClass(); //Case 1: myClass address is 1
    int a = 5;                       //Case 2: a = 5

    //just as an example
    new Button().addClickHandler(new ClickHandler() {
        
        @Override
        public void onClick(ClickEvent event) {

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

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

    myClass = new MyClass(); //Case 1: myClass address is 2
    int a = 10; //Case 2: a = 10
}

Maybe this trick gives u an idea也许这个技巧给了你一个想法

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM