I have the following class containing local inner class:
class Outer {
private boolean beep;
private int foo;
public Outer(boolean beep) {
this.beep = beep;
}
public void start(boolean beep) {
class LocalInner {
private LocalInner() {
}
public void method() {
System.out.println("Test.");
if (beep) {
System.out.println("beeping.");
}
}
}
LocalInner li = new LocalInner();
li.method();
}
}
When I compile the class javac Outer.class, and then check compiled members of Outer$1LocalInner with javap -private Outer\$1LocalClass.class, I get this:
class Outer$1LocalInner {
final boolean val$beep;
final Outer this$0;
Outer$1LocalInner();
public void method();
}
I was expecting that the constructor will be compiled to: Outer$1LocalInner(Outer, boolean). When I tried to have a look at the byte code javap -c -s -private Outer$1LocalInner.class I got this:
class Outer$1LocalInner {
final boolean val$beep;
descriptor: Z
final Outer this$0;
descriptor: LOuter;
Outer$1LocalInner();
descriptor: (LOuter;Z)V
Code:
0: aload_0
1: aload_1
2: putfield #1 // Field this$0:LOuter;
5: aload_0
6: iload_2
7: putfield #2 // Field val$beep:Z
10: aload_0
11: invokespecial #3 // Method java/lang/Object."<init>":()V
14: return
...
}
Now this is rather interesting! Let's have a closer look at these two lines:
Outer$1LocalInner();
descriptor: (LOuter;Z)V
Why is there no parameters to
Outer$1LocalInner()constructor, yet I can see in the method descriptor that it does accept two parameters as I'm expectingOuterandboolean?Why does the compiler ignores the access modifier of the local inner class? I'm declaring it as private, yet the disassembled version is package modifier.
It’s seems,
javacgenerates aSignatureAttribute:This purpose doesn’t match the scenario, as this local class does use type variables or parameterized types, but we can show that the behavior of
javapmatches.E.g., when we run
javap -s java.util.function.Supplier, we getshowing that
javapprints the method declaration as seen by the generic type system while printing the descriptor as used by the JVM is the second line. Which implies that is uses the information from theSignatureattribute when printing the method declaration.We can even force
javapto print theSignatureattribute.Using
javap -v java.util.function.Supplier:Note the line
Signature: #8 // ()TT;When I run
javap -p -v my.test.Outer$1LocalInnerwith your example, I getwhich is consistent with the theory that the method has a
Signatureattribute reporting()Vwhich leads tojavapprinting a declaration with no parameters.Using this attribute for encoding the constructor’s source code appearance, even when it is not using type variables or generic types, has not been mentioned as a purpose of this attribute and Eclipse’s compiler does not generate such an attribute.
Note that the documentation also says:
So the presence or absence of this attribute has no direct impact on the execution of the code. But when I append the following code to the end of your
startmethodit prints
when compiled with Eclipse and
when compiled with
javac, showing thatgetGenericParameterTypes()interprets thisSignatureattribute.All tests above where made with JDK 17. Since JDK 11, the outer class can access the
privateconstructor directly and therefore, the constructor declared asprivateis compiled asprivate.Prior to JDK 11, the
privatemodifier gets removed for a local class. This is different from nonlocal nested classes, wherejavackeeps theprivatemodifier and generates another non-privatedelegating constructor.