Compiler-generated classes, methods and fields in Java

SourceSource

Introduction

When we write program in Java we create some classes, methods, fields and put all of this into our source code — .java files. After these files are compiled with javac (Java Compiler) into .class files we got a bunch of Java bytecode. It turns out that not only we create classes, methods and fields in our source code, but also compiler by itself can create them when needed. In this article we’ll try to get into the compiler-generated topic, will find out what are synthetic and bridge, access flags, learn some new things from official documentation and maybe more. Let’s dive in.

Synthetic definition

Class, method and field can be synthetic, which means that it doesn’t appear in source code (therefore it is generated by compiler). Reference

Basically that simple. If something was generated by compiler (which wasn’t in source code) then compiler has to mark such generated thing as being synthetic.

NOTE: there are exceptions to such a rule, like default constructor — it won’t be marked as synthetic. The complete list of exceptions one can find in the specification.

Nested class

Classic example on when synthetic is used by a compiler is nested class (one which holds reference to parent). First we’ll take a look at inner (static) class and bytecode generated. Our example will be:

class Main {

    static Child {

    }
}

Next we’ll compile it with: javac Main.java It will produce two files: Main.class and Main$Child.class. To look at the generated code we’ll use javap:javap -v Main$Child.class

Part of generated result will be:

{
  Main$Child();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1   // Method java/lang/Object."<init>":()V
         4: return

What we see here is our class declared with default constructor (). No any reference to Main class is kept by Child class (because it is static).

If we remove static modifier and repeat the procedure, that’s what we’ll see:

class Main$Child
...
{
  final Main this$0;
    descriptor: LMain;
    flags: ACC_FINAL, ACC_SYNTHETIC

  Main$Child(Main);
    descriptor: (LMain;)V
    flags:
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #1     // Field this$0:LMain;
         5: aload_0
         6: invokespecial #2     // Method java/lang/Object."<init>":()V
         9: return

As we see we now have this$0 reference to our parent class, which is passed inside constructor. Also pay attention that that field has ACC_SYNTHETIC flag, which shows that field is synthetic — was generated by compiler.

This reference to parent is not something that is added just in case, it is useful in the cases where nested class wants to access instance methods and fields of parent class (including private ones).

For example:

class Main {

    private void foo() {}

    class Child {
    
        private void bar() {
            foo();
        }
    }
}

Here we’ll use javap with -p flag to see private members:

class Main$Child
...
{
  final Main this$0;
    descriptor: LMain;
    flags: ACC_FINAL, ACC_SYNTHETIC

  Main$Child(Main);
    descriptor: (LMain;)V
    flags:
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #1     // Field this$0:LMain;
         5: aload_0
         6: invokespecial #2     // Method java/lang/Object."<init>":()V
         9: return
      LineNumberTable:
        line 4: 0

  private void bar();
    descriptor: ()V
    flags: ACC_PRIVATE
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: getfield      #1          // Field this$0:LMain;
         4: invokestatic  #3          // Method Main.access$000:(LMain;)V
         7: return

Here we see not only synthetic field but also our private method bar is calling method acess$000 on reference to parent. But what it is? Let’s take a look at the bytecode of Main class:

class Main
...
{
  Main();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #2      // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  private void foo();
    descriptor: ()V
    flags: ACC_PRIVATE
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 2: 0

  static void access$000(Main);
    descriptor: (LMain;)V
    flags: ACC_STATIC, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method foo:()V
         4: return

Here we see that not only we have private foo method, but also static method access$000 which also has ACC_SYNTHETIC flag — so it is synthetic as well. And it was generated by compiler for Child class to be able to access private method foo.

$

Specially need to note usage of $ sign in the generated code. It is used heavily especially for generated by compiler things. Because of that is not recommended to use $ sign in class, method, field etc. names in java source code.

Anonymous class

Next example we’ll take a look at anonymous classes. We’ll try to create some runnable and see what will be in bytecode.

class Main {

    void foo() {
        new Runnable() {

            public void run() {}
        };
    }
}

We’ll see that there will be two .class files: Main.class and Main$1.class. Second one is created for our anonymous Runnable and inside we’ll see the following:

class Main$1 implements java.lang.Runnable
...
{
  final Main this$0;
    descriptor: LMain;
    flags: ACC_FINAL, ACC_SYNTHETIC

  Main$1(Main);
    descriptor: (LMain;)V
    flags:
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: putfield      #1     // Field this$0:LMain;
         5: aload_0
         6: invokespecial #2     // Method java/lang/Object."<init>":()V
         9: return
      LineNumberTable:
        line 4: 0

  public void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return

Here we see same picture: anonymous class keeps reference to parent class in a synthetic field.

Type erasure and bridge methods

In Java when using generics there is one important limitation: generics are not available at runtime. The process of removing all the information about generics at compile time is called type erasure. That means that if you use in code List in resulting bytecode it will become just List without any generic. Applying that let’s imagine what could happen in the following case. We’ll create some class and make it comparable with classes of same type.

class MyInt implements Comparable<MyInt> {

    public int compareTo(MyInt other) {
        return 0; // TODO: implement
    }
}

If we apply type erasure it would mean that effectively our class will have method compareTo(MyInt other) at the same time implementing Comparable without any type information (which means that we are working with Object). In this case as we don’t have compareTo(Object other) method nothing seems will work. Here to help comes special type of synthetic — bridge methods.

If we decompile our MyInt.class:

class MyInt extends java.lang.Object implements java.lang.Comparable<MyInt>
...
{
  MyInt();
    descriptor: ()V
    flags:
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1   // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

public int compareTo(MyInt);
    descriptor: (LMyInt;)I
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=2, args_size=2
         0: iconst_0
         1: ireturn
      LineNumberTable:
        line 4: 0

public int compareTo(java.lang.Object);
    descriptor: (Ljava/lang/Object;)I
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: checkcast     #2      // class MyInt
         5: invokevirtual #3      // Method compareTo:(LMyInt;)I
         8: ireturn

We see here that besides our compareTo(MyInt) method we have also compareTo(javalang.Object) method which is marked with ACC_SYNTHETIC flag, which means that it is synthetic. But also it has ACC_BRIDGE flag. What bridge methods do is handle type erased methods and after checking provided value types (by checkcast) redirect call to the original typed version of method.

Conclusion

Synthetic and bridge are pretty powerful concepts, which allow us to have some features which would be problematic to have if we don’t have them. Other languages such as Kotlin also rely on synthetic in some features especially when adding Java compatibility. For example default params in methods. Hope this article was useful for you.

Happy coding!