Final field and anonymous class

萝らか妹 提交于 2020-01-22 20:14:26

问题


I'm still not satisfied with explanation regarding anonymous class and final field. There were tons of questions trying to explain obvious problem but I have not found answers for all my questions :-)

Suppose following code:

public void method(final int i, int j) {
    final int z = 6;
    final int x = j;
    int k = 5;
    new Runnable() {
        public void run() {
            System.out.print(i);
            System.out.print(x);
            System.out.print(z);
            System.out.print(k);
        }
    };
}
  1. It's not possible to compile this code because of "unfinal" k property.
  2. I understand that compiler can replace z property with the declared value during compile time.

When I've searched for solution how exactly can works i and x I found this answer which says:

The compiler can then just replace the use of lastPrice and price in the anonymous class with the values of the constants (at compile time, ofcourse), and you won't have the problem with accessing non-existent variables anymore

How it could work for fields i and x if they are parameters of the method? They are not known during compile time? This approach can work for z.

On the other hand, there is an explanation regarding stack issues:

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

I would understand that anonymous class somehow copied all required content (fields) during it's creation. Missing final has obvious problem that if some code below anonymous class declaration would change the value, the execution used possible stale values.

But ok, this should solve the problem when anonymous class' method is executed out of scope of used properties.

But this approach should work even without final declaration as it just copies all fields.

Both approaches seem like independent for me. Speaking of which - and it could solve my questions - I have not found how work final method field. They are not removed from stack even if method gets finished? Seems like nonsense for me but it would explain lot of things :-)

What is the correct answer?


回答1:


It seems to me that you're getting confused between a variable being declared final, and it being a constant.

The compiler doesn't replace all references to local variables with a constant - but when the instance of the anonymous class is constructed, the current value of each relevant variable is passed to the constructor, and stored in a variable within the anonymous class. That's just as fine for a parameter as for any other kind of local variable.

So this code:

public static void method(final int x) {
    Runnable r = new Runnable() {
        @Override public void run() {
            System.out.println(x);
        }
    };
    r.run();
}

is broadly equivalent to:

public static void method(final int x) {
    Runnable r = new AnonymousRunnable(x);
    r.run();
}

private static class AnonymousRunnable implements Runnable {
    private final int x;

    AnonymousRunnable(int x) {
        this.x = x;
    }

    @Override public void run() {
        System.out.println(x);
    }
}

I've made both the method and the nested class static to avoid worrying about whether this is captured or not.

The local variable has to be final when it's captured to avoid situations which could otherwise be confusing. Suppose that weren't the case - consider this example:

void method() {
    int x = 10;
    Runnable r = new Runnable() {
        @Override public void run() {
            System.out.println(x);
        }
    };
    x = 20;
    r.run(); // Should this print 10 or 20?
}

Using the current way that anonymous classes work, but just removing the final restriction, it would print 10... but developers might expect it to print 20. Likewise, you should consider what would happen if you modified x within the run method. (As noted in Joop's answer, in Java 8 captured local variables are "effectively final" - so they act as if you've declared them to be final, but without doing so explicitly.)

As an example of a different approach, C# handles closures (for anonymous functions) in a different way, hoisting the local variables to a sort of anonymous class so that they can be modified. It's a more complex approach, but a bit more flexible. You might find my article about closures in Java and C# useful.




回答2:


Because of the need for copying the variables from the method to the anonymous class (as described), it was a language design decision to require that the variable copied be final. So assignment in either method or anonymous class would not give stale values, and the code would be more consistent.

But! In Java 8 this requirement is mitigated: final no longer is needed, if the variable is de facto final: assignments are disallowed after the variable is "copied" in the anonymous class.

This makes sense because of the many function notations. Otherwise a button's actionPerformed suddenly would need its parameter to be final, when propagating it to another functional call.




回答3:


I think you get confused because you used base types. If you think about references it should get more clear.

You are right at the creation an anonymous class copies all the references to its own context. And to be allowed to do that all the used local variables (and parameters are just another kind of local variables) have to be final. So it is not about the value, it is about the reference. And base types are a special case in java (that is sad). They treated like references in that case.

Hope this clarifies this final problem.



来源:https://stackoverflow.com/questions/18888307/final-field-and-anonymous-class

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!