1. Overview

In this quick tutorial, we’re going to shed some light on the difference between classes and objects in the Kotlin programming language.

Along the way, we’ll dig deeper and will see how each abstraction is represented at the bytecode level.

2. Class Representation

Classes in Kotlin are conceptually similar to classes in Java: They’re blueprints for creating instances:

class Person {
    // omitted
}

In the above example, the Kotlin compiler will generate a typical class definition at the bytecode level:

>> kotlinc Person.kt
>> javap -c -p -v com.baeldung.classobject.Person
public final class com.baeldung.classobject.Person
  // omitted
  flags: (0x0031) ACC_PUBLIC, ACC_FINAL, ACC_SUPER
  this_class: #2        // com/baeldung/classobject/Person
  super_class: #4       // java/lang/Object
  interfaces: 0, fields: 0, methods: 1, attributes: 2

As shown above, Kotlin classes are compiled just like Java classes with a few subtle differences such as being final by default.

Similar to Java, it’s possible to create instances or objects from concrete (not abstract) Kotlin classes:

val p = Person()

Also, we can inherit from a normal class as long as it’s open for extension:

open class Person {
    // omitted
}

class User : Person() {
    // omitted
}

3. Singleton Objects

Having exactly one instance of some abstractions, also known as singletons, may come in handy in some use cases. In Kotlin, we can use object declarations to create such singletons:

object FileSystem {
    
    fun createTempFile() {
        // omitted
    }
}

As the FileSystem is a singleton here, we can treat its members as static members in Java. Put simply, we won’t need to instantiate the FileSystem to access its members:

fun main() {
    FileSystem.createTempFile()
}

As opposed to simple classes, we don’t need to create instances of singletons. As a matter of fact, we can’t even create such instances:

val f = FileSystem()

Here the Kotlin compiler prevents us from creating an instance from a singleton object.

Even though the singleton objects themselves can extend other classes or interfaces, they can’t be inherited. To be more specific, we can write this:

object FileSystem : Serializable {
    // omitted
}

However, the following is not possible and won’t compile:

class NTFS : FileSystem

3.1. Bytecode Representation

Under the hood, the Kotlin compiler translates the FileSystem singleton object to a final class with a private constructor:

>> kotlinc FileSystem.kt 
>> javap -c -p com.baeldung.classobject.FileSystem
public final class com.baeldung.classobject.FileSystem {
  private com.baeldung.classobject.FileSystem();
    Code:
       0: aload_0
       1: invokespecial #11    // Method java/lang/Object."<init>":()V
       4: return
}

As a result, nobody else can instantiate it and nobody can inherit from it.

Also, it uses a static variable to hold the singleton instance, similar to the traditional approach in Java:

public final class com.baeldung.classobject.FileSystem {
  public static final com.baeldung.classobject.FileSystem INSTANCE;

  // omitted
}

To initialize this variable, the Kotlin compiler omits a static initializer:

static {};
    Code:
       0: new           #2    // class com/baeldung/classobject/FileSystem
       3: dup
       4: invokespecial #26   // Method "<init>":()V
       7: astore_0
       8: aload_0
       9: putstatic     #28   // Field INSTANCE:Lcom/baeldung/classobject/FileSystem;
      12: return

As shown above, it creates a FileSystem instance at first and then assigns it to the static singleton holder.

4. Conclusion

In this short tutorial, we saw how classes and objects differ in Kotlin, both at the language and bytecode level. To summarize:

  • We can instantiate from concrete classes, as opposed to singleton objects
  • We can inherit from open classes, as opposed to singleton objects

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