In the Kotlin popular programming language, there exist some unique and intriguing concepts. Among these, the Kotlin Data class companion object has gained much attention. This term might sound complex, but once broken down, it's easy to grasp. Insights about this keyword will be shared in this blog, aiming to help you understand companion objects, which include accessing class members, and static members and how to effectively use them in your Kotlin code.
In simple words, Kotlin Data class companion object is like a static member in other programming languages like Java. Yes, Kotlin doesn't have static keywords that Java developers are so accustomed to. Instead, it uses the companion object for that purpose, which allows you to use class members and private members in a very similar way that you'd normally use static members.
In Kotlin, a companion object is an object declaration inside a class. The companion keyword is used to specify that the object is a companion. This object differs from other objects in Kotlin in that you access its members through the class name, not any class instance.
1class MyClass { 2 companion object { 3 const val ID = "MyClassID" 4 } 5} 6 7fun main() { 8 println(MyClass.ID) // prints "MyClassID" 9}
In the fun main block above, MyClass.ID is being invoked without creating an instance of the class, which is essentially how we access static members in Java. This is what makes companion object in Kotlin a worthy alternative for the static keyword found in Java class.
Interestingly, a companion object can also implement interfaces, which static members in Java can't do. Let's consider a simple example:
1interface Factory<T> { 2 fun create(): T 3} 4 5class MyClass { 6 companion object : Factory<MyClass> { 7 override fun create(): MyClass = MyClass() 8 } 9} 10 11fun main() { 12 val myObject = MyClass.create() 13}
In this fun create block, we've implemented Factory interface in our companion object. This approach is typically used when we want to create a singleton pattern or a factory method.
In Kotlin, data classes enclose data and provide auto-generated functions like copy(), equals(), hashCode(), and toString(). Just declare the class followed by the "data" keyword and specify its properties:
1data class Student(val id: Int, var name: String)
Companion objects and data classes often go hand-in-hand. This is because companion objects in data classes solve the absence of static members in Kotlin.
Do note, that Kotlin data classes cannot hold static methods or fields, a feature available in other languages like Java. This is where the companion object steps in.
In the companion object block, you can define fun add or other utility functions just as you would a static function. This companion object data can be accessed directly through the class name, similar to static methods in other programming languages:
1data class Student(val id: Int, var name: String) { 2 companion object { 3 fun createGuestStudent() = Student(id = -1, name = "Guest") 4 } 5} 6 7fun main() { 8 val guest = Student.createGuestStudent() 9 println(guest) // prints "Student(id=-1, name=Guest)" 10}
In the fun main block, the function createGuestStudent() in the companion object is like a static method that does not require an instance of the class.
So, despite Kotlin's unique approach to data classes, it offers the power to emulate static methods via companion objects, ensuring a smooth transition for those accustomed to other programming languages.
As we mentioned earlier, Kotlin lacks a direct equivalent to static keyword, a widely used concept in languages like Java. However, the Kotlin companion object is the knight in shining armor that fills this gap. It allows us to access class-level functions or static methods without needing to create an instance of the class.
Java developers understand the static keyword allows static methods and fields to be accessed without creating an instance of the corresponding class. But in Kotlin, there's no direct equivalent of the static keyword. Let's consider the following Java class with Java static members:
1public class MyClass { 2 public static int staticField = 10; 3 public static void staticMethod() { 4 System.out.println("Hello from a static method!"); 5 } 6}
To access staticField and staticMethod, you don't need an instance of public class MyClass.
When trying to mimic the same functionality in Kotlin, we could use a companion object. Here's how you can do it:
1class MyClass { 2 companion object { 3 var staticField = 10 4 fun staticMethod() { 5 println("Hello from a static method!") 6 } 7 } 8} 9fun main() { 10 println(MyClass.staticField) // prints "10" 11 MyClass.staticMethod() // prints "Hello from a static method!" 12}
In fun main, we can access staticField and staticMethod just as we did in the Java code.
What adds to the power of companion objects is their ability to implement interfaces, unlike static members. So, Kotlin does not come with a static keyword. Still, companion objects gracefully manage to fill the void left by the absence of static.
The companion object shines when it comes to its role as a factory. If you're familiar with Design Patterns, you'll know quite well what a factory is. If not, fear not! A factory, in essence, is a component that handles the creation of objects.
1interface IdProvider { 2 fun getId(): String 3} 4 5class Entity private constructor(val id: String) { 6 companion object : IdProvider { 7 override fun getId(): String { 8 return "123" 9 } 10 const val id = "id" 11 fun create() = Entity(getId()) 12 } 13}
In the above example, the function create() in the companion object serves as a factory function. It calls a private constructor and returns an instance of Entity. The companion object can also implement interfaces. Here, it is implementing IdProvider.
In the companion object factory, we can control the creation of objects, which can be particularly useful when we want to create a singleton object. By marking the constructor as private, we ensure that the Entity class can only be instantiated through our companion object's create() method:
1fun main() { 2 val entity = Entity.create() 3 println(entity.id) 4}
In this fun main block, we successfully created an instance of Entity using our companion object factory method. This gives us greater control over object creation and encapsulates the complexity of creation inside the class itself.
Many developers learning Kotlin start with using companion objects as a replacement for static methods. However, in reality, the companion object offers a lot more.
In a companion object block, we can define properties, companion object members, and functions, which can be accessed directly through the class name:
1class MyClass { 2 companion object Compan { 3 val foo: String = "Hello" 4 fun sayHello() { 5 println("Hello, World!") 6 } 7 } 8} 9 10fun main() { 11 println(MyClass.foo) // "Hello" 12 MyClass.sayHello() // "Hello, World!" 13}
In the fun main block, foo and sayHello() are accessed from object Compan directly.
But did you know that companion objects could also have their names:
1class MyClass { 2 companion object NamedCompanion { } 3}
These named companion objects can be accessed via their given name, NamedCompanion, or via the class name, MyClass.
Kotlin also provides extension functions for companion objects. Consider the example:
1class MyClass { 2 companion object { } 3} 4fun MyClass.Companion.foo() { 5 println("Hello from Extension Function!") 6}
This extension function allows you to add more functionalities to the companion object without modifying its original class declaration.
Combining companion objects and Kotlin's data classes yields practical and impressive applications in real-world scenarios.
One such application is creating a factory for your data class. Let's assume we have a class MyClass representing a user with ID and name:
1data class User (val id: Int, var name: String)
We can add a companion object to this data class to provide different methods of creating an instance of User:
1data class User (val id: Int, var name: String){ 2 3 companion object { 4 fun newNormalUser(id: Int, name: String): User = User(id, name) 5 6 fun newGuestUser() = User(-1, "Guest") 7 } 8}
In this companion object, newGuestUser() and newNormalUser() function as factory methods, providing alternative ways to create instances of User:
1fun main() { 2 val normalUser = User.newNormalUser(1, "John Doe") 3 val guestUser = User.newGuestUser() 4}
In the fun main block, we can see that the companion object allows different ways of creating multiple instances, hence promoting flexibility.
Another popular use of companion objects is implementing Singleton design pattern in Kotlin. Singleton implies a class can have only one instance throughout the application's lifecycle. Kotlin makes the singleton implementation effortless and thread-safe using object declaration.
1class Singleton{ 2 companion object { 3 val instance: Singleton by lazy { Singleton() } 4 } 5}
Here the val instance block represents a single instance of a Singleton class, which is thread-safe.
Throughout our journey, an imperative truth that appeared with the "Kotlin Data class companion object" is that it opens gates to a plethora of opportunities whether it's a transition from Java to Kotlin, creating singleton instances, or implementing factory methods in data classes.
With the companion object, Kotlin does away with the classical static keyword of Java and other programming languages, and still, it manages to provide a similar functionality. Furthermore, Kotlin steps a notch higher with companion objects by enhancing them with capabilities to implement interfaces and support extension functions, thus providing even more flexibility than static methods or fields.
Starting with a basic understanding of concepts to the advanced utility of companion objects, we covered a wide range of topics. This blog post aimed to shed light on the fundamental areas of the Kotlin Data class companion object.
As Kotlin developers, it's incumbent upon us to incorporate companion objects into practical examples and enhance our Kotlin venture. Happy Coding!
Tired of manually designing screens, coding on weekends, and technical debt? Let DhiWise handle it for you!
You can build an e-commerce store, healthcare app, portfolio, blogging website, social media or admin panel right away. Use our library of 40+ pre-built free templates to create your first application using DhiWise.