Plastic straw tubes

There is a bunch of languages running on the JVM, from of course Java, to Clojure and JRuby. All of them have different syntaxes, but it’s awesome they all compile to the same bytecode. The JVM unites them all. Of course, it’s biased toward Java, but even in Java, there is some magic happening in the bytecode.

The most well-known trick comes from the following code:

public class Foo {
    static class Bar {
        private Bar() {}
    }

    public static void main(String... args) {
        new Bar();
    }
}

Can you guess how many constructors the Bar class has?

Two. Yes, you read that well. For the JVM, the Bar class declares 2 constructors. Run the following code if you don’t believe it:

Class<Foo.Bar> clazz = Foo.Bar.class;
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
System.out.println(constructors.length);
Arrays.stream(constructors).forEach(constructor -> {
    System.out.println("Constructor: " + constructor);
});

The output is the following:

Constructor: private Foo$Bar()
Constructor: Foo$Bar(Foo$1)

The reason is pretty well documented. The bytecode knows about access modifiers, but not about nested classes. In order for the Foo class to be able to create new Bar instances, the Java compiler generates an additional constructor with a default package visibility.

This can be confirmed with the javap tool.

javap -v out/production/synthetic/Foo\$Bar.class

This outputs the following:

[...]
{
  Foo$Bar(Foo$1);
    descriptor: (LFoo$1;)V
    flags: ACC_SYNTHETIC
    Code:
      stack=1, locals=2, args_size=2
         0: aload_0
         1: invokespecial #1                  // Method "<init>":()V
         4: return
      LineNumberTable:
        line 2: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LFoo$Bar;
            0       5     1    x0   LFoo$1;
}
[...]

Notice the ACC_SYNTHETIC flag. Going to the JVM specifications yields the following information:

The ACC_SYNTHETIC flag indicates that this method was generated by a compiler and does not appear in source code, unless it is one of the methods named in §4.7.8.
— The class File Format
https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.6

Theoretically, it should be possible to call this generated constructor - notwithstanding the fact that it’s not possible to provide an instance of Foo$1, but let’s put it aside. But the IDE doesn’t seem to be able to discover this second non-argumentless constructor. I didn’t find any reference in the Java Language Specification, but synthetic classes and members cannot be accessed directly but only through reflection.

At this point, one could wonder why all the fuss about the synthetic flag. It was introduced in Java to resolve the issue of nested classes access. But other JVM languages use it to implement their specification. For example, Kotlin uses synthetic to access the companion object:

class Baz() {
    companion object {
        val BAZ = "baz"
    }
}

Executing javap on the .class file returns the following output (abridged for readability purpose) :

{
  public static final Baz$Companion Companion;
    descriptor: LBaz$Companion;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  public Baz();
  [...]

  public static final java.lang.String access$getBAZ$cp();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #22                 // Field BAZ:Ljava/lang/String;
         3: areturn
      LineNumberTable:
        line 1: 0
    RuntimeInvisibleAnnotations:
      0: #15()
}
[...]

Notice the access$getBAZ$cp() static method? That’s the name of the method that should be called from Java:

public class FromJava {

    public static void main(String... args) {
        Baz.Companion.getBAZ();
    }
}

Conclusion

While knowledge of the synthetic flag is not required in the day-to-day work of a JVM developer, it can be helpful to understand some of the results returned by the reflection API.