Kotlin’s object keyword can be used in a variety of situations, all of which revolve around defining a class and creating an instance of that class at the same time. In this blog post, we’ll explore the different ways in which the object keyword can be used in Kotlin.
The object keyword in Kotlin is a versatile feature that can be used in various situations. The primary idea behind using the object keyword is that it defines a class and creates an instance (or an object) of that class simultaneously. There are three main cases where the object keyword is used:
- Object declaration is a way to define a singleton.
- Companion objects can contain factory methods and other methods that are related to this class but don’t require a class instance to be called. Their members can be accessed via class name.
- Object expression is used instead of Java’s anonymous inner class.
Now we’ll discuss these Kotlin features in detail.
Object Keyword declarations: singletons made easy
This is a way to define a singleton in Kotlin. In Java, this is typically implemented using the Singleton pattern, where a class has a private constructor and a static field holding the only existing instance of the class. In Kotlin, however, the object declaration feature provides first-class language support for defining singletons. An object declaration combines a class declaration and a declaration of a single instance of that class.
For instance, an object declaration can be used to represent the payroll of an organization, where multiple payrolls are unlikely:
object Payroll {
val allEmployees = arrayListOf<Person>()
fun calculateSalary() {
for (person in allEmployees) {
...
}
}
}
Object declarations are introduced with the object keyword. They can contain declarations of properties, methods, initializer blocks, and more. However, constructors (either primary or secondary) are not allowed in object declarations. Unlike instances of regular classes, object declarations are created immediately at the point of definition, not through constructor calls from other places in the code. Therefore, defining a constructor for an object declaration doesn’t make sense.
Inheriting from Classes and Interfaces
Object declarations can inherit from classes and interfaces. This is often useful when you need to implement an interface, but your implementation doesn’t contain any state. For instance, let’s take the java.util.Comparator interface. A Comparator implementation receives two objects and returns an integer indicating which of the objects is greater. Comparators almost never store any data, so you usually need just a single Comparator instance for a particular way of comparing objects. That’s a perfect use case for an object declaration:
object CaseInsensitiveFileComparator : Comparator<File> {
override fun compare(file1: File, file2: File): Int {
return file1.path.compareTo(file2.path, ignoreCase = true)
}
}
println(CaseInsensitiveFileComparator.compare(File("/User"), File("/user"))) // output is 0
Declaring Objects in a Class
You can also declare objects in a class. Such objects also have just a single instance; they don’t have a separate instance per instance of the containing class. For example, it’s logical to place a comparator:
data class Person(val name: String) {
object NameComparator : Comparator<Person> {
override fun compare(p1: Person, p2: Person): Int =
p1.name.compareTo(p2.name)
}
}
val persons = listOf(Person("Boby"), Person("Abhi"))
println(persons.sortedWith(Person.NameComparator))
// output is [Person(name=Abhi), Person(name=Boby)]
Using Kotlin Objects from Java
An object declaration in Kotlin is compiled as a class with a static field holding its single instance, which is always named INSTANCE. To use a Kotlin object from Java code, you access the static INSTANCE field.
// Java
CaseInsensitiveFileComparator.INSTANCE.compare(file1, file2);
here the INSTANCE field has the type CaseInsensitiveFileComparator.
Companion objects: a place for factory methods and static members
Kotlin does not have a static keyword like Java, so it uses different constructs to replace it.
One of the constructs that Kotlin uses to replace static members is package-level functions, which can replace Java’s static methods in many situations. For example, the following Java code:
public class Utils {
public static int add(int a, int b) {
return a + b;
}
}
can be replaced in Kotlin with a package-level function like this:
package mypackage
fun add(a: Int, b: Int): Int {
return a + b
}
In most cases, it’s recommended to use package-level functions. However, top-level functions can’t access private members of a class. If you need to write a function that can be called without having a class instance but needs access to the internals of a class, you can write it as a member of an object declaration inside that class. An example of such a function would be a factory method.
class User private constructor(val nickname: String) {
companion object {
fun newSubscribingUser(email: String) = User(email.substringBefore('@'))
fun newFacebookUser(accountId: Int) = User(getFacebookName(accountId))
}
}
In this example, the companion object is used to define two factory methods that can be called on the User class without creating an instance of it. The private constructor of the User class can be called from within the companion object, making it an ideal candidate to implement the Factory pattern.
Another construct that Kotlin uses to replace static members is object declarations. An object declaration creates a singleton instance of a class and can replace static fields and methods in Java. For example, the following Java code:
public class Singleton {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return INSTANCE;
}
}
can be replaced in Kotlin with an object declaration like this:
object Singleton {
fun getInstance() = this
}
In this example, the object declaration Singleton creates a singleton instance of the class and defines a method getInstance() that returns the instance.
One of the objects defined in a class can be marked with a special keyword: companion. If you do that, you gain the ability to access the methods and properties of that object directly through the name of the containing class, without specifying the name of the object explicitly. The resulting syntax looks exactly like static method invocation in Java.
Here’s an example showing the syntax:
class MyClass {
companion object {
fun myMethod() {
println("Hello from myMethod")
}
}
}
// Call myMethod() on the class
MyClass.myMethod()
If you need to define functions that can be called on the class itself, like companion-object methods or Java static methods, you can define extension functions on the companion object. For example, imagine that you have a companion object defined like this:
class MyClass {
companion object {
fun myMethod() {
println("Hello from myMethod")
}
}
}
You can define an extension function on the companion object like this:
fun MyClass.Companion.myOtherMethod() {
println("Hello from myOtherMethod")
}
You can then call myOtherMethod() on the class like this:
MyClass.myOtherMethod()
So companion objects can contain factory methods and other methods related to the class, but they don’t require a class instance to be called. The members of companion objects can be accessed via the class name. Companion objects are declared inside a class using the companion object
keyword.
Object expressions: anonymous inner classes rephrased
In Kotlin, the object
keyword can be used to declare anonymous objects that replace Java\’s use of anonymous inner classes. In this example, let\’s see how to convert a typical Java anonymous inner class—an event listener—to Kotlin using anonymous objects:
window.addMouseListener(
object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
}
)
The syntax for anonymous objects is similar to object declarations, but the name of the object is omitted. The object expression declares a class and creates an instance of that class, without assigning a name to either the class or the instance. Typically, neither is necessary because the object will be used as a parameter in a function call. However, if necessary, the object can be stored in a variable:
val listener = object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) { ... }
override fun mouseEntered(e: MouseEvent) { ... }
}
Unlike Java anonymous inner classes that can only extend one class or implement one interface, a Kotlin anonymous object can implement multiple interfaces or no interfaces.
It’s important to note that anonymous objects are not singletons like object declarations. Every time an object expression is executed, a new instance of the object is created.
Anonymous objects are particularly useful when you need to override multiple methods in your anonymous object. However, if you only need to implement a single-method interface (such as Runnable), Kotlin has support for SAM conversion. SAM conversion allows you to convert a function literal to an implementation of an interface with a single abstract method. Therefore, you can implement a single-method interface with a function literal instead of an anonymous object.
The object
keyword in Kotlin has several advantages and disadvantages.
Advantages:
- Singleton implementation: It allows you to define a Singleton pattern easily and concisely. You can declare a class and its instance at the same time, without the need for a separate class definition or initialization.
- Anonymous objects: It enables you to create anonymous objects, which can be used as an alternative to anonymous inner classes in Java. Anonymous objects can implement multiple interfaces and can override methods on the spot, without creating a separate class.
- Clean code: It can make your code cleaner and more concise, as it eliminates the need for boilerplate code that is common in Java.
Disadvantages:
- Overuse: Using the
object
keyword extensively in your code can lead to overuse and abuse, making it harder to read and maintain. - Limited functionality: It has limited functionality when compared to a full-fledged class definition. It cannot be inherited or extended, and it cannot have constructors, which limits its usefulness in certain scenarios.
- Lack of thread safety: It is not thread-safe by default, which can cause issues in multi-threaded applications. You need to add synchronization code to ensure thread safety.
Overall, the object
keyword is a powerful feature in Kotlin that can make your code more concise and eliminate boilerplate code. However, it should be used judiciously to avoid overuse and to ensure thread safety when necessary.