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

发布于 2024-10-12 21:15:06 字数 597 浏览 7 评论 0原文

  1. a 在这里只能是最终的。为什么?如何在 onClick() 方法中重新分配 a 而不将其保留为私有成员?

    private void f(按钮 b, Final int a){
        b.addClickHandler(new ClickHandler() {
    
            @覆盖
            公共无效onClick(ClickEvent事件){
                int b = a*5;
    
            }
        });
    }
    
  2. 点击时如何返回5 * a?我的意思是,

    private void f(按钮 b, Final int a){
        b.addClickHandler(new ClickHandler() {
    
            @覆盖
            公共无效onClick(ClickEvent事件){
                 int b = a*5;
                 返回b; // 但返回类型为void 
            }
        });
    }
    
  1. a can only be final here. Why? How can I reassign a in onClick() method without keeping it as private member?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(16

真心难拥有 2024-10-19 21:15:06

正如评论中所指出的,其中一些在 Java 8 中变得无关紧要,其中 final 可以是隐式的。不过,只有有效最终变量可以在匿名内部类或 lambda 表达式中使用。


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

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

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

将变量设置为 Final 消除了所有这些可能性 - 由于值根本无法更改,因此您无需担心此类更改是否可见。允许方法和匿名内部类看到彼此更改的唯一方法是使用某种描述的可变类型。这可以是封闭类本身、数组、可变包装类型……类似的东西。基本上,这有点像一个方法与另一个方法之间的通信:调用者看不到对一个方法的参数所做的更改,但对所引用的对象所做的更改由参数可见。

如果您对 Java 和 C# 闭包之间的更详细比较感兴趣,我有一篇文章 进一步深入探讨。我想在这个答案中重点关注 Java 方面:)

As noted in comments, some of this becomes irrelevant in Java 8, where final can be implicit. Only an effectively final variable can be used in an anonymous inner class or lambda expression though.


It's basically due to the way Java manages closures.

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

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. 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. I wanted to focus on the Java side in this answer :)

埋葬我深情 2024-10-19 21:15:06

有一个技巧允许匿名类更新外部作用域中的数据。

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

// ...

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

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

// ...
晚雾 2024-10-19 21:15:06

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

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

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

乔恩有完整的答案 - 我保留这个不删除,因为有人可能对 JLS 规则感兴趣)

An anonymous class is an inner class and the strict rule applies to inner classes (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. 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.

(Jon has the complete answer - I keep this one undeleted because one might interested in the JLS rule)

走野 2024-10-19 21:15:06

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

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 的粗略、受限版本的闭包)。

因为局部内部类既不是类也不是包的成员,所以它没有声明访问级别。 (但是请注意,它自己的成员具有与普通类相同的访问级别。)

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.

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

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

执手闯天涯 2024-10-19 21:15:06

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

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

initialize 方法返回后,interfaceInstance 仍保留在内存中,但参数 val 不会不是。 JVM 无法访问其作用域之外的局部变量,因此 Java 通过将 val 的值复制到同名的隐式字段来对 printInteger 进行后续调用在interfaceInstance内。据说interfaceInstance已经捕获本地参数的值。如果参数不是最终的(或实际上最终的),其值可能会发生变化,与捕获的值不同步,可能会导致不直观的行为。

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. 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. The interfaceInstance is said to have captured the value of the local parameter. 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.

我偏爱纯白色 2024-10-19 21:15:06

好吧,在 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 的意思就是:该值是不可更改的且是最终的。

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

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

Well, in Java, a variable can be final not just as a parameter, but as a class-level field, like

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.

And since it's final, Java can safely copy it to local anonymous classes. 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).

It just copies over the value of a into an implicit int called a in your anonymous class.

愁杀 2024-10-19 21:15:06

访问仅限于局部最终变量的原因是,如果所有局部变量都可以访问,那么首先需要将它们复制到一个单独的部分,内部类可以在其中访问它们并维护多个副本可变的局部变量可能会导致数据不一致。而最终变量是不可变的,因此对它们进行任意数量的复制不会对数据的一致性产生任何影响。

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.

长安忆 2024-10-19 21:15:06

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

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

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

满身野味 2024-10-19 21:15:06

匿名内部类中的方法可以在生成它的线程终止后调用。在您的示例中,内部类将在事件分派线程上调用,而不是在创建它的线程中调用。因此,变量的范围将有所不同。因此,为了保护此类变量赋值范围问题,您必须将它们声明为最终的。

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.

堇年纸鸢 2024-10-19 21:15:06
private void f(Button b, final int a[]) {

    b.addClickHandler(new ClickHandler() {

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

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

    b.addClickHandler(new ClickHandler() {

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

        }
    });
}
清醇 2024-10-19 21:15:06

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

考虑这样一种用例,您的 lambda 不是被应用,而是存储在某个位置并稍后运行。

我记得在Smalltalk中,当你进行这样的修改时,你会得到一个非法商店。

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.

Consider the use case where your lambdas instead of being apply, is stored in some place and run later.

I remember that in Smalltalk you would get an illegal store raised when you do such modification.

翻身的咸鱼 2024-10-19 21:15:06

尝试这个代码,

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

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

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;
}
东京女 2024-10-19 21:15:06

Java匿名类与Javascript闭包非常相似,但Java以不同的方式实现它。 (查看 Andersen 的答案)

因此,为了不让 Java 开发人员对那些来自 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 决定强制它是最终的,因此数据从头到尾都是“一致的”。

Java anonymous class is very similar to Javascript closure, but Java implement that in different way. (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. I guess that's why they force us to use final, this is not the JVM limitation.

Let's look at the Javascript example below:

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.

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. (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. So it will make confusion between two different outcome and behaviour (between Java and Javascript).

I believe that's why, Java decide to force it to be final, so the data is 'consistent' from the beginning to end.

待"谢繁草 2024-10-19 21:15:06

内部类内的 Java final 变量[关于]

内部类只能使用

  1. 来自外部类
  2. 最终局部变量的引用,这些变量是引用类型(例如 Object...)
  3. value(primitive) (例如 int...) 类型可以被最终引用类型包装。 其转换为一个元素数组

IntelliJ IDEA 可以帮助您在生成非静态嵌套内部类)时将 通过编译器 - 创建一个新类 - $.class 并将有界参数传递到构造函数[栈上的局部变量] 与闭包类似[Swift about]

final变量是一个不能重新赋值的变量。最终引用变量仍然可以通过修改状态来更改

如果可能的话那就很奇怪了,因为作为程序员你可以这样做

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

Java final variable inside an inner class[About]

inner class can use only

  1. reference from outer class
  2. final local variables from out of scope which are a reference type (e.g. Object...)
  3. value(primitive) (e.g. int...) type can be wrapped by a final reference type. IntelliJ IDEA can help you covert it to one element array

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]

final variable is a variable which can not be reassign. 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
}
ゞ花落谁相伴 2024-10-19 21:15:06
 public class Closure {
  public void publicMethod() {
    Integer localVariable1 = 10;

    Map<String, Integer> map = new HashMap<String, Integer>() {
      {
        put("a", localVariable1);
      }
    };

    Thread t = new Thread(new Runnable() {
      public void run() {
        System.out.println(localVariable1);
      }
    });

    List<String> list = Arrays.asList("A", "B", "C");

    Collections.sort(list, new Comparator<String>() {
      public int compare(String p1, String p2) {
        return p1.compareTo(p2);
      }
    });
  }
}
  1. 编译后,上面将产生 4 个类(3 个 AIC 和 1 个父类)。
  2. 在运行时,如果 Java 允许更新 localVariable1 或列表,则会导致编译后的类无效,并且映射将包含过时的数据
  3. 以避免这种不一致的行为java强制它是最终的或有效的最终的
 public class Closure {
  public void publicMethod() {
    Integer localVariable1 = 10;

    Map<String, Integer> map = new HashMap<String, Integer>() {
      {
        put("a", localVariable1);
      }
    };

    Thread t = new Thread(new Runnable() {
      public void run() {
        System.out.println(localVariable1);
      }
    });

    List<String> list = Arrays.asList("A", "B", "C");

    Collections.sort(list, new Comparator<String>() {
      public int compare(String p1, String p2) {
        return p1.compareTo(p2);
      }
    });
  }
}
  1. After compilation, the above would result in 4 classes (3 AIC and 1 parent class)
  2. During runtime if Java would have allowed to update localVariable1 or list, it would make the compiled classes invalid and the map would contain outdated data
  3. To avoid such inconsistent behaviour java forces it to be final or effectively final
笑脸一如从前 2024-10-19 21:15:06

也许这个技巧给你一个想法

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

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);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文