Instead of implementing my own backup application as I had planned a long time ago, I’m wandering off (re)learning Kotlin after a long absence from that language. In my defense though, I’m doing it in the context of the backup app which will not be Java as originally intended (or maybe later for comparison, who knows, I obviously can’t be trusted with my plans). Putting that aside, the most confusing concept of Kotlin for a Java developer is the object
. What is that thing doing that a class can’t do and how do we declare static fields and methods? I know it’s nothing new, but that part seems to have changed a bit since I used Kotlin about two (?) years ago. So, for me this is a refresh of old information and also something new and by writing about it I will engrave it in my brain once and for all. And your confusion will hopefully turn into some productive… fusion… of some sort… or so.
Where’s the Java static?
The first question a Java developer coming to Kotlin probably needs answered is how to declare a static variable or function. This will be the first time you come in contact with object
or, more specifically, the companion object
. Let me show you a totally artificial Java snippet and how it would be implemented in Kotlin.
Java
class ArtificialExampleJ {
static final int THE_ANSWER = 42;
}
// Usage from Java
@Test
public void test_static_member() {
assertThat(ArtificialExampleJ.THE_ANSWER).isEqualTo(42);
}
// Usage from Kotlin
@Test
fun test_java_artificial_static() {
assertThat(ArtificialExampleJ.THE_ANSWER).isEqualTo(42)
}
Kotlin
// Declaration
class ArtificialObjectDeclarations {
companion object {
const val THE_ANSWER = 42
}
}
// Usage from Kotlin
@Test
fun test_companion_object() {
assertThat(ArtificialObjectDeclarations.THE_ANSWER).isEqualTo(42)
}
// Usage from Java
@Test
public void test_kotlin_artificial_companion_object() {
assertThat(ArtificialObjectDeclarations.THE_ANSWER).isEqualTo(42);
}
What is important to understand is the fact that Kotlin really creates an object here. But since it is a companion
object it can be referenced by the parent class’ object. Were you to remove the companion
from the object (every time I read or write Companion
I am reminded of the Firefly TV series) then you have to give it a name and also reference the field through that name.
// Declaration
class ArtificialObjectDeclarations {
object NamedObject {
const val NAMED_ANSWER = 42
}
}
// Usage from Kotlin
@Test
fun test_named_object() {
assertThat(ArtificialObjectDeclarations.NamedObject.NAMED_ANSWER)
.isEqualTo(42)
}
// Usage from Java
@Test
public void test_kotlin_artificial_named_object() {
assertThat(ArtificialObjectDeclarations.NamedObject.NAMED_ANSWER)
.isEqualTo(42);
}
Such objects are singletons and are not required to be contained in a class. Kotlin is much more relaxed than Java in this regard. You can declare multiple classes or objects in the same file.
// Declaration
class ArtificialObjectDeclarations {
companion object {
const val THE_ANSWER = 42
}
object NamedObject {
const val NAMED_ANSWER = 42
}
}
object ArtificialSingleton {
private var c = 0
fun tellMeTheValue() : Int {
// I even managed to get some C++ into Kotlin.
return c++
}
}
// Usage
@Test
fun test_singleton() {
assertThat(ArtificialSingleton.tellMeTheValue()).isEqualTo(0)
assertThat(ArtificialSingleton.tellMeTheValue()).isEqualTo(1)
}
Here’s the Java equivalent as decompiled from Bytecode. I have to admit that I was pretty surprised when I saw the outcome.
// ArtificialObjectDeclaration.class
public final class ArtificialObjectDeclarations {
public static final int THE_ANSWER = 42;
public static final com.thecodeslinger.ArtificialObjectDeclarations.Companion Companion = new com.thecodeslinger.ArtificialObjectDeclarations.Companion((DefaultConstructorMarker)null);
}
// ArtificialObjectDeclarations$Companion.class
public final class ArtificialObjectDeclarations$Companion {
private ArtificialObjectDeclarations$Companion() {
}
// $FF: synthetic method
public ArtificialObjectDeclarations$Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
// ArtificialObjectDeclarations$NamedObject.class
public final class ArtificialObjectDeclarations$NamedObject {
public static final int NAMED_ANSWER = 42;
public static final ArtificialObjectDeclarations$NamedObject INSTANCE;
private ArtificialObjectDeclarations$NamedObject() {
}
static {
ArtificialObjectDeclarations$NamedObject var0 = new ArtificialObjectDeclarations$NamedObject();
INSTANCE = var0;
}
}
The compiler creates the static
s as a standalone field instead of the (companion
)object
. So what looks natural in Kotlin looks like a waste of memory in Java because you have the static final int
and an instance of the object
.
Digging a bit deeper I found the following information in the Kotlin docs (section "Static fields").
Properties declared as const (in classes as well as at the top level) are turned into static fields in Java
Even though object
is not mentioned here, it explains what is happening. The examples in the docs show object
s though, so I’m not interpreting something into this sentence. Note that this behavior is different for functions. Section "Static methods" of the previously linked documentation page explains that those have to be called through an instance of the object from Java unless the function is annotated with @JvmStatic
.
Take a look at this Kotlin code.
class ArtificialObjectDeclarations {
companion object {
const val THE_ANSWER = 42
fun companionFunction() {
return THE_ANSWER
}
}
}
It is turned into this Java equivalent.
// ArtificialObjectDeclarations.class
public final class ArtificialObjectDeclarations {
public static final int THE_ANSWER = 42;
public static final com.thecodeslinger.ArtificialObjectDeclarations.Companion Companion = new com.thecodeslinger.ArtificialObjectDeclarations.Companion((DefaultConstructorMarker)null);
}
// ArtificialObjectDeclarations$Companion.class
public final class ArtificialObjectDeclarations$Companion {
public final void companionFunction() {
return 42;
}
private ArtificialObjectDeclarations$Companion() {
}
// $FF: synthetic method
public ArtificialObjectDeclarations$Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
As you can see, the function (now called a method) only exists on the Companion
class and as such has to be called this way in Java.
@Test
public void test_kotlin_compaion_function() {
assertThat(ArtificialObjectDeclarations.Companion.companionFunction())
.isEqualTo(42);
}
All of these examples are called "Object Declaration". Kotlin also has a concept of "Object Expression" which I’ll handle in another post.