Java 为什么在匿名类中只能访问final变量?

Java 为什么在匿名类中只能访问final变量?,java,event-handling,anonymous-class,Java,Event Handling,Anonymous Class,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; } }); } 单击时如何

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

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

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

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

    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的值,并在需要的地方使用它

    您的原因答案是:

    局部内部类实例绑定到主类,可以访问其包含方法的最终局部变量。当实例使用其包含方法的最终局部时,即使变量已超出范围(这实际上是Java粗糙、有限的闭包版本),变量也会保留其在创建实例时持有的值


    由于本地内部类既不是类或包的成员,因此不会使用访问级别声明它。(但是,要清楚的是,它自己的成员具有与普通类类似的访问级别。)

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


    这主要是由于Java的管理方式

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

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

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


    如果您对Java和C#闭包之间更详细的比较感兴趣,我有一个更深入的例子。在这个回答中,我想把重点放在Java方面:)

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

    在Java中,变量可以是final的,不仅作为参数,而且作为类级字段,如

    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
    的意思是:该值是不可更改的且为final

    由于它是最终版本,Java可以安全地将它复制到本地匿名类。您没有得到一些对int的引用(特别是因为在Java中不能引用像int这样的原语,只能引用对象

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

    匿名类是一个内部类,严格的规则适用于内部类:

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

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


    (-我不删除这个,因为你可能对JLS规则感兴趣)

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

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

    访问权限仅限于局部最终变量的原因是,如果所有局部变量都可以访问,则首先需要将它们复制到一个单独的部分,在该部分中,内部类可以访问这些变量并维护其多个副本
    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);
    
    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;
    }
    
    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();
        }
    }
    
    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
    
    //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
    }