Having a class like this:
public class Sample1 {
public class Inner {
private int f;
}
void go() {
Inner in = new Inner();
int value = in.f;
}
}
The byte-code for the go
method (before java-11) calls that known synthetic method:
static int access$000(nestmates.Sample1$Inner);
descriptor: (Lnestmates/Sample1$Inner;)I
flags: ACC_STATIC, ACC_SYNTHETIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field f:I
4: ireturn
from:
0: new #2 // class nestmates/Sample1$Inner
3: dup
4: aload_0
5: invokespecial #3 // Method nestmates/Sample1$Inner."<init>":(Lnestmates/Sample1;)V
8: astore_1
9: aload_1
10: invokestatic #4 // Method nestmates/Sample1$Inner.access$000:(Lnestmates/Sample1$Inner;)I
I've known about this for quite some time, but I've never questioned myself why this is like this? Why javac has to do this in the first place, and why not directly getField
and this indirection via the static method. Thank you.
CodePudding user response:
Because JVMS doesn't allow access to private members of a different class, whether it is an inner class or not. JDK 11 added the concept of nestmates to overcome this limitation.
CodePudding user response:
The reason is that the outer and inner classes compile to different class files, which means they can't access each others' private members.
The synthetic method is generated to effectively widen the access from private to package-private, but it achieves that via unnecessary indirection.
In Java 11, they introduced a new concept for this purpose which does allow classes in different files to access each others' private members in certain cases (i.e. this one).
See the JEP: https://openjdk.org/jeps/181
CodePudding user response:
It's a simple conclusion based on a number of factors:
At the JVM (class file /
java.exe
) level, inner classes simply do not exist. At all. Javac 'fakes' it, by naming yourInner
classSample1$Inner
(that dollar isn't a rendering, that's its actual JVM-level name, a $ is just a symbol, just as valid asI
orn
), adding a parameter of typeSample1
as first param to all constructors of Inner, replacingnew Sample1()
withnew Sample1(this)
, replacinginstanceOfOuter.new Sample1()
withnew Sample1(instanceOfOuter)
, having afinal
field of typeSample1
(which those constructors set by using that first param), translating all calls to outer methods as being invoked on that field (given that there's no "outer this" at the JVM level, as there is no outer class), and so on. You can see all this usingjavap -c
.private
members cannot be accessed by any type other than itself, therefore, given that an inner class ceases to be that once compilation has concluded (because the JVM doesn't have the inner class concept in the first place), it needs a package-private (or protected or public, as you want) way to get at it.The JVM also has no idea what synthetic means. It's a flag, yes, and the JVM completely ignores. It doesn't know what it implies, it doesn't affect how code is to be run or interpreted at all. It's
javac
that knows what synthetic means. Namely: flag anything you made up to glue together the carefully managed fakery to make inner classes seem like a thing when they are purely a java-the-language (i.e. compiler) feature and don't exist at the java-the-runtime level - and when reading class files, be blind to them. Act like these synthetic methods do not exist. As in, if javac is asked to compile code that attempts to call a synthetic method, act the same way as if compiling code that attempts to call a non-existant method.
As a comment has already noted, more recent versions of java do introduce this notion of inner classes at the JVM level, but not quite by enshrining the idea of 'inner class' in the JVM Specification, but by having the 'nestmates' concept, which lets class files list other class names that get to 'see' and call/interact with private elements. Modern javacs, if targeting modern JVMs ('modern' defined here as: "Has the nestmates feature available"), will use nestmates and forego all the synthetics.