TL; DR
Java developers new to Kotlin are often confused by the lack of static members. Which options Kotlin
provides in order to replace the static
keyword? Let's summarize for easy reference:
Java static… | Replace with… |
---|---|
void main() | Top-level function |
Constants | Top-level constants |
Methods in utility class | Top-level functions |
Factory methods | Companion object functions |
Private methods | Private companion object functions or private top-level functions in same file |
Singleton accessors | Object instance |
Continue reading for the details.
Where is my precious static?
When Java developers start to code in Kotlin, one of the first question is: “How can I define a static function or property?". But there is no such keyword in Kotlin. Why?
Several modern languages dropped statics for the same reason they dropped explicit support for primitive types: simplify the language by removing special cases. As far as I know, Scala is the first JVM language that did that and Kotlin adopted a similar approach.
Java static methods and fields are attached to the class where they are declared, not its instances. Static member lives in a separate world than instance members, governed by different rules:
Instance methods | Static methods | |
---|---|---|
Scope | Instance | Class |
Can override open methods in superclass | Yes | No (but can shadow them) |
Can implement methods in interface | Yes | No |
Dispatch | Dynamic, by actual runtime type | Static, by type known at compile-time |
Having them inside the same language construct, the class
, is mixing together two different concerns: on one side the
dynamic behavior of individual instances and, on the other side, global static code.
It's no surprise that beginners have a hard time understanding static members: first we explain them the
object-oriented mantra that classes are like blueprints to define how individual objects can behave, but then we go on
and tell them that the blueprints themselves can have non-object-oriented behaviors on their own…
In Kotlin, a class can only define the behavior of instances: it's really like a blueprint. Static or global functions are defined separately, outside of the class definitions.
Let's now look in detail at the options Kotlin provides.
Top-level functions & properties
In Java everything must be defined inside a class. Fortunately Kotlin allows to define top-level functions, outside any class definition, directly in a .kt file.
Function main()
Every Java beginner starts by writing this “Hello world” application.
|
|
There is a lot of boilerplate for such a simple program. With Kotlin top-level functions we can write the main()
function with a minimal amount of ceremony.
|
|
Get rid of utility classes
I cannot count how many *Utils
or *Helper
classes I've created in Java during my career, with only static methods
inside…
|
|
Let's get rid of the utility class by rewriting that with a top-level function:
|
|
That was easy!
Top-level functions & properties are associated with their package. This also means we cannot declare 2 top-level functions or properties with the same name in different files of the same package.
We can access a top-level function from another package by importing it from the package where it was defined:
|
|
It's often a good idea to consider converting such top-level functions to extension functions, if they can be thought as extensions of one of the arguments.
|
|
Constants
A property can also be declared top-level, but like in Java, we should use them only for constants or immutable values.
|
|
Private top-level functions
In Java, when refactoring long methods, I like to extract small referentially transparent private methods. Referentially transparent functions are easier to reason about and refactor because they forbid side effects, i.e their output purely depend on their input arguments. To ensure they stay referentially transparent, I make them static any access to instance fields.
|
|
In Kotlin I extract them into private top-level functions instead (or in some cases into companion functions, see below). Private top-level functions and properties are visible only in the file declaring them.
|
|
From Java
From Java, top-level members can be called like static members on a class named after the Kotlin file where the
top-level function is declared, with the Kt
suffix. Assuming we declared lowerCaseCount()
in a file called
TopLevel.kt
, then in Java we'll call it like this:
|
|
If you dislike the Kt
suffix, you can rename this class generated by Kotlin using the annotation
@file:JvmName
:
|
|
Object singletons
Kotlin natively supports the Singleton pattern with Object declarations. Because it's an actual value, the Object can extend classes or interfaces and its functions are dynamically dispatched. Nonetheless they can be called very much like static members in Java.
|
|
From Java
From Java, by default, you can access Object members through the static INSTANCE
field:
|
|
When an Object function is not overriding a function from a superclass or
interface, which necessarily implies dynamic dispatch, then it can be made static by using the @JvmStatic
annotation:
|
|
The compiler now generates an actual static member which can be called directly from Java:
|
|
Companion objects
Singleton objects are very nice but what if your Java class have static functions that access private non-static members? How to port that to Kotlin? Companion objects are your friends.
Static factory methods
Companion objects are singletons values that are declared inside a class and have a close relationship with it. Members of the companion object can be called using the name of the accompanied class. Moreover the companion object's members can access any private member of the class instances. Companion objects are therefore especially useful for factory methods.
|
|
From Java
By default the companion object members are accessible from Java through the static Companion
field:
|
|
Again, we can use the @JvmStatic
annotation to allow direct static access to the companion members from Java:
|
|
|
|
Conclusion
By removing the static
keyword from its vocabulary, Kotlin avoid mixing up concerns and provide several other
constructs that cover the same functionality while being more powerful (singleton and companion objects) or needing
less ceremony (top-level functions and properties). However, developers new to Kotlin need to learn how to map the use
cases that were covered by static
to the appropriate Kotlin features. Hopefully this article can help.