-1

inner class uses outer local variable is through its constructor like this InnerClass(String outerVar)

inner class uses outer field is through this.

I read it from an article that programmers who use Java are not used to to having variable changed both sides, otherwise it causes a AAD (Action At remote Distance).

If that is true, outer field can be changed both sides causes the same AAD problem.

What are the thinkings behind local var is final while field is not.


void test() {
  var a = "a"; // outer local variable 
  class InnerClass {
     void print() {sout(a);} // InnerClass(String a)
  }
}

String a = "a" // outer field
void test() {
  class InnerClass {
    void print() { a = newA;} // OuterClass.this.a = newA
  }
  sout(a) // newA
}
Tiina
  • 4,285
  • 7
  • 44
  • 73
  • See the discussion under [this answer](https://stackoverflow.com/a/50341404/5133585), which is about lambdas, but also applies to inner classes (that's kind of what lambdas are). – Sweeper Jun 25 '21 at 08:34
  • @Sweeper thank you for the link. That is exactly my question. Why final local variable lower the risk, but mutable field does not. :)) I thought in order to lower the risk, both should be immutable. – Tiina Jun 25 '21 at 08:42
  • @ernest_k why `this` is final? – Tiina Jun 25 '21 at 09:07

2 Answers2

3

Local variables are allocated on the stack and invalidated as soon as the method ends while fields are allocated on the heap.

If the inner class tried to access a local variable after the method ended, it would access invalid memory (possibly overwritten by something else). As fields are allocated on the heap, this is no problem because those are not invalidated until there is no reference to the object.

If the variable is final, the inner class can just access a copy as the copy will always be the same as the original variable (cannot be changed).

Using a copy with non-(effectively) final variables would cause logical errors if you would modify the variable later in the method as the copy wouldn't catch up (or the original value wouldn't ve updated if the copy us changed).

Objects are allocated on the heap, meaning those are shared between methods and threads. Objects and their instance variables exist as long as there is a reference to them.

However, not that caution should be taken when working with multiple threads as you may encounter synchronisation and visibility issues (not the topic of this question, though).

dan1st
  • 12,568
  • 8
  • 34
  • 67
  • I accept your answer as why local var should be final as they lower the risk of bug, my confusion is more on the side of "why outer field can be non-final". Besides, if local var is not final, then it is not a copy, it should be the same object (unless String) I think. – Tiina Jun 25 '21 at 08:56
  • @Tiina For local class using outside local variable, it will always be using a copy, since a local variable is not an object/field that can be referenced outside the method declaring it. – Andreas Jun 25 '21 at 09:04
  • Objects are allocated on the heap, meaning those are shared between methods and threads. Those exist as long as there is a reference to it. (Note that you should be careful with multithreading as there are different pitfalls) – dan1st Jun 25 '21 at 09:40
2

An inner class has a hidden unnamed field referencing the outer class. For the sake of this answer, we'll name it $outer.

The first version:

public class OuterClass {
  void test() {
    var a = "a"; // Local variable
    class InnerClass {
      void print() { sout(a); } // sout("a")
    }
    new InnerClass().print();
  }
}

That actually gets compiled to:

public class OuterClass {
  void test() {
    final String a = "a";
    new OuterClass$0$InnerClass(this, a).print();
  }
}
final class OuterClass$0$InnerClass {
  private final OuterClass $outer;
  private final String a;
  OuterClass$0$InnerClass(OuterClass $outer, String a) {
    this.$outer = $outer;
    this.a = a;
  }
  void print() { sout(this.a); } // sout("a")
}

As you can see, the local variable gets copied. The enforcement of final is to prevent the confusion that would occur if the local variable were changed after the value was copied.


The second version:

public class OuterClass {
  String a = "a"; // Outer field
  void test() {
    class InnerClass {
      void setA(String newA) { a = newA; }
    }
    new InnerClass().setA("b");
    sout(a); // sout("b")
  }
}

Which gets compiled to:

public class OuterClass {
  String a = "a"; // Outer field
  void test() {
    new OuterClass$0$InnerClass(this).setA("b");
    sout(a); // sout("b")
  }
}
class OuterClass$0$InnerClass {
  private final OuterClass $outer;
  OuterClass$0$InnerClass(OuterClass $outer) {
    this.$outer = $outer;
  }
  void setA(String newA) { this.$outer.a = newA; }
}

Since the value of field a is not copied, there is no need to enforce final behavior.

kameshsr
  • 327
  • 3
  • 13
Andreas
  • 154,647
  • 11
  • 152
  • 247
  • So changing variable value in InnerClass would make people think the outer value is also changed, but it is not, this counterintuitive phenomena easily causes bug, so local var is `final`. On the other side, changing field value would change the outer value, which is intuitive, and thereafter does not cause bug, so it is not final. – Tiina Jun 25 '21 at 09:47
  • Shouldn't it be `OuterClass$InnerClass` instead of `OuterClass$0$InnerClass` as `$0` is for anonymous classes? – dan1st Jun 25 '21 at 09:48
  • @dan1st The so-called "InnerClass" is actually a [local class](https://docs.oracle.com/javase/tutorial/java/javaOO/localclasses.html), which is like a "named anonymous class" (yeah, that's a contradiction in terms), but the name can only be used in the method where the class declaration is, i.e. it has the same scope as a *local variable*, hence the name *local class*. – Andreas Jun 25 '21 at 19:22