1. Overview

In this quick tutorial, we’re going to see how we can use companion objects to statically initialize a class.

First, we’ll take a quick look at static initialization in Java. Then we’re going to achieve the same in Kotlin. Along the way, we’ll take a peek at the bytecode representation of the Kotlin solution.

2. Static Initialization

In Java, to initialize static components of a class, we can use static initializer blocks:

static {
    // put static initializers here
}

There are no static members and static initializers in Kotlin, at least similar to what Java has. However, we can use a companion object to achieve the same thing in Kotlin:

class Static {
    
    companion object {
        lateinit var answer: String
        
        init {
            answer = "42"
            println("Initialized")
        }
    }
}

All we have to do is declare an init block inside the companion object of a class. This way, we get the same behavior as Java’s static block.

Put simply, when the JVM is going to initialize the enclosing class, it will execute the init block inside the companion object.

3. Bytecode Representation

To take a peek at the generated bytecode, first, we should compile the Kotlin class using kotlinc:

$ kotlinc Static.kt

Now, let’s see the bytecode using the javap tool:

$ javap -c -p -v Static 
Compiled from "Static.kt"
public final class com.baeldung.staticinit.Static {
  public static java.lang.String answer;
  
  // truncated
}

As shown above, the variable in the companion object is declared as a static field inside the enclosing class. Moreover, the companion object itself is a static inner class inside in the same enclosing class:

InnerClasses:
  public static final #15= #37 of #2; // Companion=class Static$Companion of class Static

Each companion object is a singleton because the compiler creates a single instance of it:

public final class com.baeldung.staticinit.Static {

  public static final Static$Companion Companion;
  // truncated 
}

And finally, the init block itself is compiled as a static initializer block under-the-hood:

static {};
    Code:
       0: new           #37     // class Static$Companion
       3: dup
       4: aconst_null
       5: invokespecial #40     // Method Static$Companion."<init>":(LDefaultConstructorMarker;)V
       8: putstatic     #42     // Field Companion:LStatic$Companion;
      11: ldc           #44     // String 42
      13: putstatic     #20     // Field answer:LString;
      16: ldc           #46     // String Initialized
      18: astore_0
      19: iconst_0
      20: istore_1
      21: getstatic     #52     // Field System.out:LPrintStream;
      24: aload_0
      25: invokevirtual #58     // Method PrintStream.println:(LObject;)V
      28: return

Before executing the logic in the init block, the JVM creates an instance of the companion class (index 0 to 5), and stores it as a static member inside the enclosing class (index 8). Only after that will the JVM execute the initializer logic.

The bottom line is, despite their differences at the language level, static initializer blocks and companion object initializers are the same at the bytecode level.

4. Conclusion

In this short tutorial, first, we had a quick refresher on what Java static initializer looks like. Then we saw we can use companion objects and init blocks to achieve the same thing in Kotlin. Finally, by looking at the generated bytecode, we learned that static and init blocks are represented the same under-the-hood in both Kotlin and Java.

As usual, all the examples are available over on GitHub.