Kotlin
A concise multiplatform language developed by JetBrains
The Companions to Come
We are working on extensions to our companions mechanism to unlock new code patterns and improve interoperability across different platforms. This post outlines these new features and provides preliminary guidance on migration.
Companion blocks and extensions
Many programming languages provide a notion of class or type members – functionality that does not belong to each particular value of a class, but to the class itself. Constants, utilities, and factory methods are often in this group.
val v1 = Vector(1.0, 2.0) // a regular vector val v2 = Vector.ZERO // the constant zero vector val v3 = Vector.unit(angle = PI / 2) // unit vector with an angle
Previously, companion objects were used to define these members. From Kotlin 2.5.0 on, you can also use an experimental companion block, which is very close syntactically, but with profound changes in compilation.
data class Vector(val x: Double, val y: Double) {
companion {
val ZERO = Vector(0.0, 0.0)
}
}
In fact, these members may not be defined directly on the class itself. You can also use an experimental companion extension to declare new class members in a type, even if you don’t control them.
companion fun Vector.unit(angle: Double) = Vector(cos(angle), sin(angle))
The design of companion blocks and extensions overcomes the limitations imposed by companion objects. First of all, you can define companion extensions for any class or interface – whether you control it or not, whether it originally comes from Kotlin or from Java, or whether it has a companion object or not.
Second, the compilation strategy resembles that of static members in other platforms. For example, on the JVM, companion blocks are compiled as static members. This has additional implications for Kotlin Multiplatform: you can now define expected companion block members and actualize them using a Java class that contains static members.
If you want to know more about this upcoming language feature, the corresponding KEEP proposal contains all the information.
Using experimental companions
Companion blocks and extensions are experimental features in Kotlin 2.5.0. That means that you need to pass an additional compiler flag to use the feature:
-Xcompanion-blocksto allow companion blocks.-Xcompanion-blocks-and-extensionsto allow both companion blocks and extensions.
Using the second feature causes the compiler to produce pre-release binaries, which means libraries using companion extensions may not be consumed as dependencies until the feature becomes stable. This restriction does not apply to exposing companion blocks, although consumers of such blocks still need to enable the companion blocks feature.
What about my companion objects?
This leads to a natural question: What is the role of companion objects in a language with companion blocks and extensions? There are two answers to this question:
- On the one hand, most usages of companion objects would be better served by companion blocks, so new code may prefer the latter to the former.
- On the other hand, there are a few use cases that are only served by companion objects. For example, if your companion needs to implement a particular interface, companion objects are the only possibility, since they compile to a full-fledged class.
There’s no need to migrate, though. Companion objects are an integral part of Kotlin, and support for them is fully guaranteed.
If you prefer to move to companion blocks, removing the object keyword should be enough in most cases – but note that the project and any code depending on it need to be recompiled. Other tooling in the ecosystem may not be ready for companion blocks, although we’re trying to ensure major players provide this support as soon as possible.