Currently, inner classes are compiled into distinct class files, but the compiler will insert synthetic helper methods when there is an access to a private
member across nested classes. The synthetic method itself will have package-private access and perform the access to the private
member within its own class.
This can be demonstrated with private
methods as their execution can be traced and will show the execution of these helper methods:
public class OuterClass {
static class InnerClass {
private static void test() {
OuterClass.privateMethod();
}
}
private static void privateMethod() {
Thread.dumpStack();
}
public static void main(String[] args) {
InnerClass.test();
}
}
Running this program will print:
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1329)
at OuterClass.privateMethod(OuterClass.java:9)
at OuterClass.access$000(OuterClass.java:2)
at OuterClass$InnerClass.test(OuterClass.java:5)
at OuterClass$InnerClass.access$100(OuterClass.java:3)
at OuterClass.main(OuterClass.java:12)
These nested classes are compiled into two distinct class files OuterClass.class
and OuterClass$InnerClass.class
. You can see that the compiler has inserted the synthetic method access$100
into OuterClass$InnerClass
which allows the main
method of OuterClass
to invoke the private
method test
of the inner class. This inner class method in turn invoked a synthetic method access$000
in the outer class which allows the invocation of privateMethod()
in OuterClass
.
Note that this kind of access is different to the access to private
members performed with Java 8’s lambda expressions and method references. For the member access performed in that context, no helper methods are generated by the compiler and the way the JVM makes the access possible is intentionally unspecified, but we can say that for Oracle’s current JRE implementation, there will be a runtime-generated class which is indeed capable of bypassing the access restriction of private
members, e.g.
import java.util.function.Consumer;
public class OuterClass {
static class InnerClass {
static final Consumer<Runnable> TEST_METHOD=InnerClass::test;
private static void test(Runnable outerMethod) {
outerMethod.run();
}
}
private static void privateMethod() {
Thread.dumpStack();
}
public static void main(String[] args) {
System.out.println(System.getProperty("java.version"));
InnerClass.TEST_METHOD.accept(OuterClass::privateMethod);
}
}
As of 1.8.0_65
, it prints:
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1329)
at OuterClass.privateMethod(OuterClass.java:12)
at OuterClass$InnerClass.test(OuterClass.java:8)
at OuterClass.main(OuterClass.java:16)
Not showing such helper methods, but also filtering out the runtime-generated classes. Changing privateMethod()
to
private static void privateMethod() {
for(StackTraceElement e:Thread.getAllStackTraces().get(Thread.currentThread()))
System.out.println("\tat "+e);
}
reveals
at java.lang.Thread.dumpThreads(Native Method)
at java.lang.Thread.getAllStackTraces(Thread.java:1603)
at OuterClass.privateMethod(OuterClass.java:12)
at OuterClass$$Lambda$2/135721597.run(Unknown Source)
at OuterClass$InnerClass.test(OuterClass.java:8)
at OuterClass$InnerClass$$Lambda$1/471910020.accept(Unknown Source)
at OuterClass.main(OuterClass.java:16)
with the generated classes having fancy names like OuterClass$InnerClass$$Lambda$1/471910020
and OuterClass$$Lambda$2/135721597
which are accessing the private
members. Note that the generation of these classes has been triggered by the classes which have the right to access these private
members, which has been checked before allowing to create such function objects.