In the Java language, the static keyword is used primarily to indicate that methods and properties belong to an object rather than to an instance of the object. The static keyword is also used to create singletons, a very common design pattern that allows you to create a unique instance of an object that can be accessed and shared by other objects.

Kotlin could have implemented this design pattern more elegantly. You can implement singletons using only one keyword: object. The next section will show you the difference between implementing singletons in Java and Kotlin, and how to implement singletons in Kotlin without using the static keyword, Then I will explain the underlying implementation mechanism when using object.

First, let’s talk about the background of this application scenario — why do we need a singleton?

What is a singleton?

A singleton is a design pattern that guarantees a single instance of a class and provides an interface that is globally accessible to that object. Singletons are ideal for scenarios where objects need to be shared in different parts of the application, and where initializing instances is expensive.

A singleton in Java

To ensure that there is only one instance of a class, you need to control how objects are created. To have one and only one instance of a class, you define the constructor as private and create a publicly accessible static object reference. At the same time, you generally don’t create singletons at startup time, because objects that use singletons are very resource-intensive to create. To do this, you need to provide a static method that checks to see if the object has been created. The static method must either return the instance it created earlier, or call the constructor and return the instance.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

public class Singleton{
    private static Singleton INSTANCE;
    private Singleton(){}
    public static Singleton getInstance(){
        if (INSTANCE == null){
            INSTANCE = new Singleton();
        }
        return INSTANCE;
    }
    private int count = 0;
    public int count(){ returncount++; }}Copy the code

The code above doesn’t look like much, but there’s a big problem: it’s not thread-safe. At some point, a thread may be suspended just after running an if statement, while another thread calls the method and creates a singleton. The thread that was suspended will continue running and create another instance.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

public class Singleton{
    private static Singleton INSTANCE = new Singleton();
    private Singleton(){}
    public static Singleton getInstance(){
        if (INSTANCE == null) {                // A check
            synchronized (Singleton.class) {
                if (INSTANCE == null) {        // Double checkINSTANCE = new Singleton(); }}}return INSTANCE;
    }
    private int count = 0;
    public int count(){ returncount++; }}Copy the code

To solve the problem of thread synchronization, you can use secondary check locking. In the secondary check lock, if the instance is empty, the synchronized keyword is used to create a lock and the secondary check is performed to ensure that the current instance is still empty. If the instance is still empty at this point, the singleton is created. However, this is not enough. Singletons also need to use the volatile keyword. The volatile keyword tells the compiler that the variable may be changed asynchronously by concurrently running threads.

This leads to a lot of template code that needs to be repeated every time you create a singleton. With so much code for such a simple task, enumerations are often used when creating singletons in Java.

Singleton in Kotlin

So let’s go back to Kotlin. There are no static methods or static fields in Kotlin, so how do we create singletons in Kotlin?

In fact, Android Studio/IntelliJ can help us understand this. When you convert Java singleton code to Kotlin code, all static properties and methods are moved to the Companion Object.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

class Singleton private constructor() {
    private var count = 0
    fun count(a): Int {
        return count++
    }

    companion object {
        private var INSTANCE: Singleton? =
            Singleton()// Double check

        // Single Checked
        val instance: Singleton?
            get() {
                if (INSTANCE == null) { // First check
                    synchronized(Singleton::class.java) {
                        if (INSTANCE == null) { // Double check
                            INSTANCE =
                                Singleton()
                        }
                    }
                }
                return INSTANCE
            }
    }
}
Copy the code

The transformed code is good enough to do what we want, but it actually makes the code cleaner. You can remove the constructor and companion keywords from the Object code. The difference between Object and Companion objects will be explained later in this article.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

object Singleton {
    private var count: Int = 0

    fun count(a) {
        count++
    }
}
Copy the code

When you want to use the count() method, you call it through the Singleton object. In Kotlin, Object is a special class that has only one instance. If you create a class using the object keyword instead of class, the Kotlin compiler makes the constructor private, creates a static reference to the Object class, and initializes the reference in a static code block.

The static code block is called once when a static field is first accessed. Even without the synchronized keyword, the JVM handles static code blocks similarly to synchronized code blocks. When the Singleton class is initialized, the JVM acquires a lock from the synchronized code block so that it cannot be accessed by other threads. When the synchronization lock is released, the Singleton instance has been created and the static code block is no longer run. This ensures that there is only one Singleton instance, which satisfies the Singleton requirement. In this way, object is thread-safe and allows for delayed creation of first access.

Let’s take a look at the decompiled Kotlin bytecode to see how the underlying implementation works.

To view the Bytecode of a Kotlin class, choose Tools > Kotlin > Show Kotlin Bytecode. After the Kotlin bytecode is displayed, click Decompile to see the decomcompiled Java code.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

public final class Singleton {
   private static int count;
   public static final Singleton INSTANCE;
   public final int getCount() {returncount; }public finalvoid setCount(int var1) {count = var1; }public final int count() {
      int var1 = count++;
      return var1;
   }
   privateSingleton() {} static { Singleton var0 = new Singleton(); INSTANCE = var0; }}Copy the code

However, object has some limitations. The object declaration cannot contain a constructor, that is, no arguments can be passed to it. Even if it supports passing arguments, non-static arguments in the constructor cannot be accessed by static code blocks, so the arguments passed in cannot be used.

⚠️ Like other static methods, a static initialization code block can access only the static properties of a class. The static code block is called before the constructor, so the static code block cannot access the object’s properties or the arguments passed to the constructor.

companion object

Companion Object is similar to Object. Companion Objects are often declared in classes, and their properties are accessible through the host class name. Companion Objects do not need to be named. If you define a Companion Object name, you can also access its class members by name.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

class SomeClass {
    / /...
    companion object {
        private var count: Int = 0
        fun count(a) {
            count++
        }
    }
}
class AnotherClass {
    / /...
    companion object Counter {
        private var count: Int = 0
        fun count(a) {
            count++
        }
    }
}
// Scenarios with no names defined
SomeClass.count()
// Define the scenario for the name
AnotherClass.Counter.count()
Copy the code

For example, here we have two similar class definitions, named and nameless Companion Objects. The count() method can be called through SomeClass, just like a static member of SomeClass; Or you can call the count() method by using Counter, just like a static member of AnotherClass.

Decommounting the Companion Object results in an inline class with a private constructor. The host class initializes an inner class that only the host class can access through a composite constructor. The host class keeps a public reference to a Companion Object that can be accessed by other classes.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

public final class AnotherClass {
    private static int count;
    public static final AnotherClass.Counter Counter = new AnotherClass.Counter((DefaultConstructorMarker)null);

    public static final class Counter {
        public final void count() {
            AnotherClass.count = AnotherClass.count + 1;
        }
        private Counter() { }
        // $FF: synthetic method
        public Counter(DefaultConstructorMarker $constructor_marker) {
            this();
        }
    }
    
    public static final class Companion {
        public final void count() {
            AnotherClass.count = AnotherClass.count + 1;
        }
        private Companion() {}
    }
}
Copy the code

The Object expression

So far, we have used the object keyword when declaring objects, but it can also be used with object expressions. When used as an expression, the object keyword helps you create anonymous objects and anonymous inner classes.

For example, when you need a temporary object to hold some data values, you can immediately declare the objects and initialize them with the required values, and then access them later.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

val tempValues = object : {
    var value = 2
    var anotherValue = 3
    var someOtherValue = 4
}

tempValues.value += tempValues.anotherValue
Copy the code

In the generated code, this operation is converted to an anonymous Java class, and the object is marked to hold the anonymous object and its getters and setters.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

<undefinedtype> tempValues = new Object() {
    private int value = 2;
    private int anotherValue = 3;
    private int someOtherValue = 4;

    Getters and setters for x,y, and z
    / /...
};
Copy the code

The object keyword creates anonymous classes without using template code. You can use an Object expression, and the Kotlin compiler generates a wrapper class declaration to create an anonymous class.

<! -- Copyright2019 Google LLC.
SPDX-License-Identifier: Apache-2.0 -->

/ / Kotlin code
val t1 = Thread(object : Runnable {    
    override fun run(a) {
         // Logical code
    }
})
t1.start()

// Decompiled Java code
Thread t1 = new Thread((Runnable)(new Runnable() {
     public void run() {
     
     }
}));
t1.start();
Copy the code

The object keyword helps you create thread-safe singletons, anonymous objects, and anonymous classes with less code. With Object and Companion Objects, Kotlin generates all the code needed to implement something like the static keyword. In addition, you can avoid the hassle of template code and create anonymous objects and classes just by using object expressions.