Kotlin Discovered: About statics
You never touched Groovy, nor did you jump on the Scala train. Clojure never attracted you; and you heard about Ceylon long after the language had already died. You are one of those old-fashioned Java folks! But now, after all those years, you want to join the cool Kotlin kids. So, where to start? Let’s discover the language together by decompiling it to Java code. Today: the Companion Object and top level declarations!
Today: Companion Object and top level declarations
If you’ve been writing Kotlin for a while, you naturally come across the question concerning static
functions.
These do not seem to exist at all…
Well, you are quite right!
The Kotlin language designers eliminated static functions, by introducing top level declarations and companion objects.
A top level declaration is a simple constant outside a class:
const val ORIGINAL_COLOR = "#FFFFF"
class Shape
In your Kotlin code you can import this constant from anywhere. The decompiled Java code shows the compiler just adds an extra class around the top level declaration[1]:
public final class ShapeKt {
@NotNull
public static final String ORIGINAL_COLOR = "#FFFFF";
}
public final class Shape {}
As expected, the ORIGINAL_COLOR has been marked as a static String. The top level declaration does not look that exciting, it’s just a little shorter than the Java way. But come to think of it, the idea of using properties not bound to a class makes a lot of sense. The static keyword is Java a little bit peculiar when you contemplate about it. Java is an object-oriented language, but static fields and methods are not bound to an instance, but to the class itself. Conceptually, they don’t really seem to fit.
If you still were to try to fit statics into an object-oriented world, one could argue once a class has defined static members, there is another singleton object that contains all the static properties of the class[2]. That is exactly what Kotlin offers in addition to top-level declarations, because there is the companion object as well:
class Shape {
companion object {
const val ORIGINAL_COLOR = "#FFFFF"
}
}
You can call it just like a static field in Java, so Shape.ORIGINAL_COLOR
in our example.
However, the Java code is much larger than the top-level declaration variant:
public final class Shape {
@NotNull
public static final String ORIGINAL_COLOR = "#FFFFF";
@NotNull
public static final Companion Companion = new Companion((DefaultConstructorMarker)null);
public static final class Companion {
private Companion() {}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
Why all the extra dazzle? Shouldn’t the static ORIGINAL_COLOR not be enough? And why should you even use the companion object at all? Well, the companion object gives you extra inheritance powers[3]:
abstract class Shape {
companion object {
@JvmStatic
protected val defaultColor = "#FFFFF"
}
}
class Text : Shape() {
companion object {
private const val defaultColor = "#FF0000"
}
fun getDefaultColor() = defaultColor
}
class Box : Shape() {
fun getDefaultColor() = defaultColor
}
This is something you can’t do in regular Java, of course! If you look at the simplified Java source code, the existence of the Companion object within the bytecode becomes clear:
public abstract class Shape {
private static final String defaultColor = "#FFFFF";
public static final class Companion {
protected final String getDefaultColor() {
return Shape.defaultColor;
}
}
}
public final class Box extends Shape {
public final String getDefaultColor() {
return Shape.Companion.getDefaultColor();
}
}
public final class Text extends Shape {
public final String getDefaultColor() {
return "#FF0000";
}
}
Well, that’s enough for one day. Stay tuned for more!