From Java to Kotlin: life without static

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.

1
2
3
4
5
public class HelloWorld {
  public static void main(String[] args) {
      System.out.println("Hello World");
  }
}

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.

1
2
3
4
// Look Ma! No class!
fun main() { // 'args: Array<String>' parameter is optional
  println("Hello World")
}

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…

1
2
3
4
5
6
7
8
9
//Java
public class StringUtils {
  private StringUtils() { /* Forbid instantiation of utility class */ }   

  public static int lowerCaseCount(String value) {
      return value.chars().reduce(0, 
              (count, current) -> count + (Character.isLowerCase(current) ? 1 : 0));
  }
}

Let's get rid of the utility class by rewriting that with a top-level function:

1
2
3
package toplevel

fun lowerCaseCount(value: String): Int = value.count { it.isLowerCase() }

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:

1
2
3
4
5
6
7
package anotherpackage

import toplevel.lowerCaseCount

fun main() {
  println(lowerCaseCount("Hello World"))
}

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.

1
2
3
4
5
fun String.lowerCaseCount(): Int = this.count { it.isLowerCase() }

fun main() {
  println("Hello World".lowerCaseCount())
}

Constants

A property can also be declared top-level, but like in Java, we should use them only for constants or immutable values.

1
2
3
4
5
6
7
// This is OK
const val CONSTANT_STRING = "CONSTANT"
val READONLY_LIST = listOf("value1", "value2")

// NOT OK: avoid public mutable top-level properties
var mutableValue = "currentValue"
val mutableList = mutableListOf("value1", "value2")

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.

1
2
3
4
5
6
7
8
9
// Java
public class MyClass {
  private String myString;
  public void doSomething() {
    int count = lowerCaseCount(myString);
    ...
  }
  private static int lowerCaseCount(String value) { ... } // private static pure function
}

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.

1
2
3
4
5
6
7
8
class MyClass(private val myString: String) {
  fun doSomething() {
    val count = myString.lowerCaseCount()
    ...
  }
}

private fun String.lowerCaseCount(): Int = ...

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:

1
System.out.println(TopLevelKt.lowerCaseCount("Hello Java"));

If you dislike the Kt suffix, you can rename this class generated by Kotlin using the annotation @file:JvmName:

1
2
3
4
@file:JvmName("TopLevel")
package toplevel

fun lowerCaseCount(value: String): Int = ...

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
interface Counter {
  fun count(value: String): Int
}

object LowerCaseCounter : Counter { // can implement an interface
  override fun count(value: String) = value.count { it.isLowerCase() }
}
object UpperCaseCounter : Counter { // another implementation of the same interface
  override fun count(value: String) = value.count { it.isUpperCase() }
}

fun main() {
  // functions on singleton objects can be called like Java static methods
  println(LowerCaseCounter.count("Hello World")) // prints 8
  println(UpperCaseCounter.count("Hello World")) // prints 2
  // But singletons are values and can be assigned to variables and passed as arguments
  val counter = if (someCondition) LowerCaseCounter else UpperCaseCounter
  println(counter.count("Hello Kotlin Everywhere")) // dynamic dispatch
}

From Java

From Java, by default, you can access Object members through the static INSTANCE field:

1
System.out.println(LowerCaseCounter.INSTANCE.count("Hello World"));

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:

1
2
3
4
object StringOps {
  @JvmStatic
  fun lowerCaseCount(value: String): Int = value.count { it.isLowerCase() }
}

The compiler now generates an actual static member which can be called directly from Java:

1
System.out.println(StringOps.lowerCaseCount("Hello World"));

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.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Rocket private constructor() {
  private fun attachPropellers() { ... }

  companion object {
    fun build(): Rocket {
      val rocket = Rocket()  // can call private constructor
      rocket.attachPropellers() // can call private function
      return rocket
    }
  }
}

fun main() {
  val rocket = Rocket.build() // Companion function called using the accompanied class name
}

From Java

By default the companion object members are accessible from Java through the static Companion field:

1
Rocket rocket = Rocket.Companion.build();

Again, we can use the @JvmStatic annotation to allow direct static access to the companion members from Java:

1
2
3
4
5
6
class Rocket private constructor() {
  companion object {
    @JvmStatic
    fun build(): Rocket = ...
  }
}
1
2
// Then in Java
Rocket rocket = Rocket.build();

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.

kotlin  java 

comments powered by Disqus