1. Introduction

Kotlin borrowed many ideas from other languages; one of such constructs is the object.

In this quick article, we’ll see what objects are and how can be used.

2. Objects in Kotlin

In Kotlin, as in almost all JVM languages, there’s the concept of a class as the core of the Object-Oriented Programming model. Kotlin introduces the concept of an object on top of that.

Whereas a class describes structures that can be instantiated as and when desired and allows for as many instances as needed, an object instead represents a single static instance, and can never have any more or any less than this one instance.

This is useful for various techniques, including singleton objects and simple packaging up of functionality for encapsulation:

object SimpleSingleton {
    val answer = 42;
    fun greet(name: String) = "Hello, $name!"
}

assertEquals(42, SimpleSingleton.answer)
assertEquals("Hello, world!", SimpleSingleton.greet("world"))

Objects also offer full support for visibility modifiers, allowing for data hiding and encapsulation as with any other class:

object Counter {
    private var count: Int = 0

    fun currentCount() = count

    fun increment() {
        ++count
    }
}
Counter.increment()
println(Counter.currentCount())
println(Counter.count) // this will fail to compile

In addition, objects can extend classes and implement interfaces. In doing so, they are effectively singleton instances of parent classes, exactly as expected.

This can be very useful for cases where we have a stateless implementation and there’s no need for creating a new instance every time — e.g. Comparator:

object ReverseStringComparator : Comparator<String> {
    override fun compare(o1: String, o2: String) = o1.reversed().compareTo(o2.reversed())
}

val strings = listOf("Hello", "World")
val sortedStrings = strings.sortedWith(ReverseStringComparator)

3. What Is a Companion Object?

Companion objects are essentially the same as a standard object definition, only with a couple of additional features to make development easier.

A companion object is always declared inside of another class. Whilst it can have a name, it doesn’t need to have one, in which case it automatically has the name Companion:

class OuterClass {
    companion object { // Equivalent to "companion object Companion"
    }
}

Companion objects allow their members to be accessed from inside the companion class without specifying the name.

At the same time, visible members can be accessed from outside the class when prefixed by the class name:

class OuterClass {
    companion object {
        private val secret = "You can't see me"
        val public = "You can see me"
    }

    fun getSecretValue() = secret
}

assertEquals("You can see me", OuterClass.public)
assertEquals("You can't see me", OuterClass.secret) // Cannot access 'secret'

4. Static Fields

The main use for companion objects is to replace static fields/methods known from Java. However, these fields aren’t automatically generated as such in the resulting class file.

If we need them to be generated, we need to use the @JvmStatic annotation on the field instead, which will then generate the bytecode as expected:

class StaticClass {
    companion object {
        @JvmStatic
        val staticField = 42
    }
}

Without doing this, the static field staticField isn’t easily accessible from Java code.

Adding this annotation generates the field exactly as needed for a standard static field, allowing for full interoperability from Java if necessary.

This means that the above generates a static method getStaticField() on the StaticClass class.

5. Conclusion

Objects in Kotlin add a whole extra layer that we can use, further streamlining our code and making it easier to develop.

Companion objects then take this even further, allowing for cleaner code that is easier to maintain and work with.

As always, code snippets can be found over on over on GitHub.

3 Comments
Oldest
Newest
Inline Feedbacks
View all comments
Comments are closed on this article!