0

If you make an anonymous class inside a function, and attempt to use an argument inside the static class, then javac/your IDE will throw an error saying that you cannot use a variable inside an anonymous class unless it is declared final....yet all arguments are effectively final due to java's pass reference by value semantics. So why don' the same semantics apply here?

E.g. Error:

public static Optional<Node> getLayerByName(String name) {
    Optional<Node> optional = FluentIterable.from(someCollection).firstMatch(new Predicate<Node>() {
        @Override
        public boolean apply(Node node) {
            return name.equals(node.getProperty(LayerProperties.NAME.name()));
        }
    });
}

But this is fine:

public static Optional<Node> getLayerByName(final String name) {
    Optional<Node> optional = FluentIterable.from(someCollection).firstMatch(new Predicate<Node>() {
        @Override
        public boolean apply(Node node) {
            return name.equals(node.getProperty(LayerProperties.NAME.name()));
        }
    });
}

As far as I know this is the only time that Javac will force to you declarer a function argument as final. There have been many questions on SO about whether its right to declare arguments as final, given that they are effectively final due to java's scoping rules. And its generally considered bad practice to change an argument reference inside the scope of a function anyway.

I am just curious about why the design choice to force you to declare it final, as if it would be confusing in this instance to change the reference inside function scope while the anonymous class uses the function argument, yet it is not considered confusing enough to automatically declare all function references as final.

EDIT - This is a question about Java's choice of semantics, not a question about why the variable has to be final.

E.g. for lots of programmers from a non java background might expect

public static changeReference(String thing){
    thing = "changed";
}

public static void main(String[] args) {
    String thing = "orgininal";
    changeReference(thing)
    System.out.println(thing); //prints original
}

to actually print "changed". In this sense all references in arguments are final, in that nothing which changes the reference inside a function affects the reference outside of the function. It seems like it would improve clarity if we just always put final in, then it would be clear that the changeReference function doesn't do anything because the compiler would tell you. But if you are to allow the local rescoping of argument variables, is this really any more or less confusing than if:

public static Optional<Node> getLayerByName(String name, Collection<Node> someCollection) {
    name = "changed"
    Optional<Node> optional = FluentIterable.from(someCollection).firstMatch(new Predicate<Node>() {
        @Override
        public boolean apply(Node node) {
            System.out.println(name);
            return name.equals(node.getProperty(LayerProperties.NAME.name()));
        }
    });
}

public static void main(Collection<Node> someCollection){
    getLayerByName("original", someCollection); // prints "original" never "changed"
}

was allowed an prints "original" rather than changed? Why did the designer's think one of these behaviours was more confusing than the other and force extra semantics on you to deal with it, or why did they not enforce the same semantics in these two virtually identical situations?

phil_20686
  • 4,000
  • 21
  • 38
  • 1
    Method parameters can't be made final because it would break backwards compatibility. Unfortunately. – Kayaman Aug 19 '15 at 11:31
  • possible duplicate of [Why Java inner classes require "final" outer instance variables?](http://stackoverflow.com/questions/3910324/why-java-inner-classes-require-final-outer-instance-variables) –  Aug 19 '15 at 11:39
  • I don't think this is a duplicate - I know why the argument *has* to be final, its because method arguments are on the stack but objects variables are on the heap, so the final preserves the illusion that there is only one variable. The question is, why did they force you to declare it here, when all method arguments are effectively final anyway. Why doesn't the compiler just use the method argument silently. – phil_20686 Aug 19 '15 at 11:49
  • Why did the designers of java think a certain way? I'm not sure we can answer that. – takendarkk Aug 19 '15 at 12:04
  • 1
    @phil_20686, it's already implemented in java-8. Just upgrade and be happy without the final keyword. – Tagir Valeev Aug 19 '15 at 12:18

2 Answers2

0

The reason: Such a variable is copied under the same name into the anonymous class. And that is because of the life-time of the local variable is limited to the function call. It is a variable slot on the stack into which the value (here String object reference) is placed. The inner class instance can live even in another thread.

So actually there are two variables with the same name. Now if you could assign to one variable, the other variable would need some synchronization (it it still lives, that is!). That would be no longer a simple machine level thing, so it was decided that "the" variable was required to be effectively final to prevent assignments further on. First really final, later in java 8 on with lambdas, effectively final.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
0

In your comment you ask,

The question is, why did they force you to declare it here, when all method arguments are effectively final anyway.

So if I understand correctly, you are saying that all method arguments are effectively final because they only exist in the context of that method, and so you are asking why, in the examples you provided above, does Java throw an error if you do not declare your variables as final.

The answer as I understand it is that this choice is due to the use of the inner class, or anonymous inner class in this case. Having an inner class access a variable in its enclosing method makes it appear as though the inner class is using that same variable. When in fact, the inner class is only using a copy of that variable, and any changes the inner class makes to said variable will not persist in the enclosing method. Java just wants to make sure the developer doesn't expect any changes to the variable in the inner class to effect the variable outside the inner class, and to any unsuspecting developer, that's exactly what looks like would happen.

(I recently wrote a short article about this if you want to read into it a little bit more.)

Brendan L
  • 1,436
  • 14
  • 13