7

Given this code:

class Foo {}

public class Test {
        public Foo makeFoo(String p, String q) {
                return new Foo(){
                        public void doSomething() {
                                System.out.println(p);
                        }
                };
        }
}

When you compile that and run javap -c -p 'Test$1.class', you get this:

Compiled from "Test.java"
class Test$1 extends Foo {
  final java.lang.String val$p;

  final Test this$0;

  Test$1(Test, java.lang.String);
    Code:
       0: aload_0
       1: aload_1
       2: putfield      #1                  // Field this$0:LTest;
       5: aload_0
       6: aload_2
       7: putfield      #2                  // Field val$p:Ljava/lang/String;
      10: aload_0
      11: invokespecial #3                  // Method Foo."<init>":()V
      14: return

  public void doSomething();
    Code:
       0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
       3: aload_0
       4: getfield      #2                  // Field val$p:Ljava/lang/String;
       7: invokevirtual #5                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      10: return
}

When the anonymous class is created, the variable p is captured into val$p (as expected, because it's needed), and the variable q is not (as expected, because it's not needed). However, Test.this is captured into this$0 even though it isn't needed. Is this mandated by the Java specification, or is it just the way it happens to work? Why does it work this way?

  • on a side not, with lambda expressions this has changed - as they don't capture anything unless they have to – Eugene Jan 05 '18 at 10:18

2 Answers2

6

Because it is an inner class, and because

An instance i of a direct inner class C of a class or interface O is associated with an instance of O, known as the immediately enclosing instance of i. The immediately enclosing instance of an object, if any, is determined when the object is created (§15.9.2).

JLS 8.1.3.

There is no exception for 'even if they don't need to'.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • 3
    @Eugene Citation please. In any case the question is about anonymous inner classes, not lambdas, and so is this answer. – user207421 Jan 05 '18 at 11:15
  • I tried to find if this is indeed specified in the JLS, but could not. nevertheless that is still the case; unless you capture something external from a lambda's point, nothing will be captured additionally – Eugene Jan 06 '18 at 21:25
  • @Eugene If you can't find a specification in the JLS, it *isn't* 'the case'. From my hasty reading of [JLS #15.7](https://docs.oracle.com/javase/specs/jls/se9/html/jls-15.html#jls-15.27), a lambda expression isn't an anonymous inner class at all, so your comment is irrelevant. – user207421 Jan 08 '18 at 09:19
  • indeed it's not part o the `JLS` specification as the runtime is free to choose whatever it wants underneath (via `invokedynamic`), but the point is that it's still a class. So the compiler in case of a lambda expression can detect if it should capture anything or not, in case of a anonymous class it does not. whether you find this irrelevant of not, is entirely up to you – Eugene Jan 08 '18 at 15:20
  • @Eugene A comment about lambdas is irrelevant to a question and answer about inner classes. If you have some reason to believe otherwise please state it clearly. – user207421 Jan 15 '18 at 00:43
  • my point was - that it _does not_ have to be like this and lambda's are proof of that; otherwise this answer is correct. almost 2 years later, I never gave you a proper upvote. sorry. – Eugene Oct 11 '19 at 14:51
4
  1. Because it is simpler to do it that way. Fewer code paths in the bytecode compiler, for example.

  2. Because if they treated the cases where this capture is necessary or unnecessary as different cases (i.e. by altering the effective constructor signature) then this would create huge problems for code that needs to create instances using reflection, byte-code engineering, etc.

Now the flipside of this is that it probably doesn't matter. The bytecodes are JIT compiled, and the JIT compiler should be capable of optimizing away unused variables (like this$0). If it is worthwhile to optimize away the passing of the hidden variable, this will be done by the JIT compiler as well.

Note this: You cannot make sound judgments on Java code efficiency by looking at the bytecode sequences. You really need to look at the native code emitted by the JIT compiler.


UPDATE - The stuff I wrote above about the JIT compiler's capability is speculative. However, if it turns out that there is a fundamental reason why the JIT compiler cannot optimize away an unused this$0, then that is most likely also a reason why the bytecode compiler can't do this either. (I am thinking of what happens when you debug the application.)

Eugene
  • 117,005
  • 15
  • 201
  • 306
Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
  • I've always heard that anonymous classes are a potential source of memory leaks. Are you saying that's not true? – shmosel Jan 05 '18 at 03:14
  • In some cases they definitely are, in others they *may not be*. It will be platform dependent; e.g. on how good the JIT compiler is. If you want / need to know, look at the native code for your platform. But the fact that this is liable to be platform dependent, it would be unwise to assume that anon classes don't leak. – Stephen C Jan 05 '18 at 03:20
  • @StephenC I can't tell why this has not changed in java-8 though, for example lambda expressions do not capture anything unless they have to, seems like it would be doable for anonymous inner classes too – Eugene Jan 05 '18 at 10:23