Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Final field and anonymous class

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?

like image 900
Martin Podval Avatar asked Sep 19 '13 07:09

Martin Podval


People also ask

What is the anonymous class?

An anonymous class cannot access local variables in its enclosing scope that are not declared as final or effectively final. Like a nested class, a declaration of a type (such as a variable) in an anonymous class shadows any other declarations in the enclosing scope that have the same name.

What is the inner and anonymous inner class?

An anonymous inner class is an inner class which is declared without any class name at all. In other words, a nameless inner class is called an anonymous inner class. Since it does not have a name, it cannot have a constructor because we know that a constructor name is the same as the class name.

Why do we use anonymous class in Java?

An anonymous class must implement all the abstract methods in the super class or the interface. An anonymous class always uses the default constructor from the super class to create an instance.

What are abstract classes and anonymous classes?

In simple words, a class that has no name is known as an anonymous inner class in Java. It should be used if you have to override a method of class or interface. Java Anonymous inner class can be created in two ways: Class (may be abstract or concrete).


2 Answers

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.

like image 136
Joop Eggen Avatar answered Oct 08 '22 07:10

Joop Eggen


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.

like image 40
Jon Skeet Avatar answered Oct 08 '22 09:10

Jon Skeet