
This article will discuss the use of statics in Kotlin.
Let's start.
There is no static in Kotlin!
This is stated in the official documentation.
And it seems that this could have been the end of the article. But excuse me, how is it? After all, if you insert Java code into a file on Kotlin in Android Studio, then a smart converter will make magic, turn everything into code in the required language and everything will work! But what about full compatibility with Java?
')
In this place, any developer, learning about the lack of static in Kotlin, will get into the documentation and forums to deal with this issue. Let's sort it out together, thoughtfully and painstakingly. I will try to keep as few questions as possible on this topic by the end of this article.
What is the static behavior in Java? There are:
- static class fields
- class static methods
- static nested classes
Let's do an experiment (this is the first thing that comes to mind).
Create a simple Java class:
public class SimpleClassJava1 { public static String staticField = "Hello, static!"; public static void setStaticValue (String value){ staticField = value; } }
Everything is easy here: in the class we create a static field and a static method. We make everything public for experiments with access from the outside. We associate the field and method logically.
Now we will create an empty Kotlin-class and try to copy into it all the contents of the SimpleClassJava1 class. The answer to the formed question about the answer is “yes” and see what happened:
class SimpleClassKotlin1 { var staticField = "Hello, static!" fun setStaticValue(value: String) { staticField = value } }
It seems that this is not exactly what we need ... To verify this, we transform the byte-code of this class into Java-code and see what happened:
public final class SimpleClassKotlin1 { @NotNull private String staticField = "Hello, static!"; @NotNull public final String getStaticField() { return this.staticField; } public final void setStaticField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.staticField = var1; } public final void setStaticValue(@NotNull String value) { Intrinsics.checkParameterIsNotNull(value, "value"); this.staticField = value; } }
Yes. Everything is exactly as it seemed. No static here and does not smell. The converter simply chopped off the static modifier in the signature, as if it had never existed. Just in case, we immediately make a conclusion: you should not blindly trust the converter, sometimes it can give unpleasant surprises.
By the way, about half a year ago, converting the same Java code into Kotlin would have shown a slightly different result. So again: be careful with automatic conversion!
Experiment further.
Go to any class on Kotlin and try to call static elements of a Java class in it:
SimpleClassJava1.setStaticValue("hi!") SimpleClassJava1.staticField = "hello!!!"
Here is how! Everything is beautifully called, even autocomplete code tells us everything! Pretty curious.
We now turn to the more substantive part. Indeed, the creators of Kotlin decided to move away from static in the form in which we used to use it. Why it was done this way and not otherwise we will not argue - there are plenty of disputes and opinions on this issue on the net. We'll just figure out how to live with it. Naturally, we are not just deprived of statics. Kotlin gives us a set of tools with which we can compensate for the lost. They are suitable for internal use. And the promised full compatibility with Java code. Go!
The quickest and easiest thing you can realize and start using is the alternative that we are offered instead of static methods, the functions of the package level. What it is? This is a function that does not belong to any class. That is, this kind of logic that is in a vacuum somewhere in the package space. We can describe it in any file inside the package of interest. For example, let's call this file JustFun.kt and place it in the package
com.example.mytestapplication
package com.example.mytestapplication fun testFun(){
Convert the bytecode of this file to Java and take a look inside:
public final class JustFunKt { public static final void testFun() {
We see that in Java a class is created whose name takes into account the name of the file in which the function is described, and the function itself becomes a static method.
Now, if we want to call the
testFun
function from the class (or the same function) in
package com.example.mytestapplication
(that is, the same package as the function) in Kotlin, we can simply access it. If we call it from another package, then we must import, familiar to us and usually applicable to classes:
import com.example.pavka.mytestapplication.testFun
If we talk about the function call t
estFun
from Java code, then the import of the function must always be done, no matter from which package we call it:
import static com.example.pavka.mytestapplication.ForFunKt.testFun;
The documentation states that in most cases, instead of static methods, we just need to use the functions of the package level. However, in my personal opinion (which does not have to coincide with the opinion of everyone else), this method of implementing statics is only suitable for small projects.
It turns out that these functions do not belong explicitly to any class. Visually, their call looks like a call to a class method (or its parent) in which we are, which can sometimes be confusing. Well and the main thing - function with such name can be only one in a package. Even if we try to create the function of the same name in another file, the system will give us an error. If we talk about large projects, then we often have, for example, different factories that have the same static methods.
Let's look at other alternatives for implementing static methods and fields.
Recall what a static class field is. This is a class field belonging to the class in which it is declared, but not belonging to a specific instance of the class, that is, it is created in a single copy for the entire class.
Kotlin offers us for these purposes to use some additional entity, which also exists in a single copy. In other words - singleton.
For declaring singletons, Kotlin has an object keyword.
object MySingltoneClass {
Such objects are initialized lazily, that is, at the time of the first access to them.
Ok, there are singletones in Java too, and here is static?
For any class in Kotlin, we can create a companion or companion object. A certain singleton tied to a particular class. This can be done using 2 keywords
companion object
together:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){
Here we have the class
SimpleClassKotlin1
, inside which we declare a singleton using the object keyword and bind it to an object, inside which it is declared the keyword companion. Here you can pay attention to the fact that, unlike the previous singleton declaration (MySingltoneClass), the name of the singleton class is not indicated. In case the object is declared as a companion, it is allowed not to indicate its name. Then he will be automatically assigned the name
Companion
. If needed, we can get a companion class instance in this way:
val companionInstance = SimpleClassKotlin1.Companion
However, an appeal to the properties and methods of the companion class can be done directly through the reference of the class to which it is associated:
SimpleClassKotlin1.companionField SimpleClassKotlin1.companionFun("Hi!")
This is much like calling static fields and classes, isn't it?
If necessary, we can assign a name to a companion class, but in practice this is done very rarely. Among the interesting features of the accompanying classes, it can be noted that, like any regular class, it can implement interfaces, which can sometimes help us add a bit more order to the code:
interface FactoryInterface<T> { fun factoryMethod(): T } class SimpleClassKotlin1 { companion object : FactoryInterface<MyClass> { override fun factoryMethod(): MyClass = MyClass() } }
A companion class can have only one class. However, no one forbids us to declare any number of singleton objects within a class, but in this case we must explicitly indicate the name of this class and, accordingly, indicate this name when referring to the fields and method of this class.
Speaking of classes declared as object, we can say that we can also declare nested objects in them, but we cannot declare a companion object in them.
It's time to look under the hood. Take our simple class:
class SimpleClassKotlin1 { companion object{ var companionField = "Hello!" fun companionFun (vaue: String){ } } object OneMoreObject { var value = 1 fun function(){ } }
Now we decompile its bytecode in Java:
public final class SimpleClassKotlin1 { @NotNull private static String companionField = "Hello!"; public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null); public static final class OneMoreObject { private static int value; public static final SimpleClassKotlin1.OneMoreObject INSTANCE; public final int getValue() { return value; } public final void setValue(int var1) { value = var1; } public final void function() { } static { SimpleClassKotlin1.OneMoreObject var0 = new SimpleClassKotlin1.OneMoreObject(); INSTANCE = var0; value = 1; } } public static final class Companion { @NotNull public final String getCompanionField() { return SimpleClassKotlin1.companionField; } public final void setCompanionField(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); SimpleClassKotlin1.companionField = var1; } public final void companionFun(@NotNull String vaue) { Intrinsics.checkParameterIsNotNull(vaue, "vaue"); } private Companion() { }
Look what happened.
The property of a companion object is represented as a static field of our class:
private static String companionField = "Hello!";
It seems that this is exactly what we wanted. However, this field is private and accessed through the getter and setter of our companion class, which is represented here as a
public static final class
, and its instance is represented as a constant:
public static final SimpleClassKotlin1.Companion Companion = new SimpleClassKotlin1.Companion((DefaultConstructorMarker)null);
The companionFun function did not become a static method of our class (probably it should not). It remained a singleton function, initialized in the SimpleClassKotlin1 class. However, if you think about it, then logically it is about the same thing.
With the
OneMoreObject
class
OneMoreObject
situation is very similar. It is worth noting that here, unlike a companion, the value class field did not move to the
SimpleClassKotlin1
class, but remained in
OneMoreObject
, but it also became static and received a generated getter and setter.
Let's try to comprehend all of the above.
If we want to implement static fields or class methods in Kotlin, then for this you should use the companion object declared inside this class.
Calling these fields and functions from Kotlin will look quite similar to calling statics in Java. And what will happen if we try to call these fields and functions in Java?
Autocomplete tells us that the following calls are available:
SimpleClassKotlin1.Companion.companionFun("hello!"); SimpleClassKotlin1.Companion.setCompanionField("hello!"); SimpleClassKotlin1.Companion.getCompanionField();
That is, here we will not get anywhere from the direct indication of the name of the partner. Accordingly, it uses the name assigned to the default companion object. Not very comfortable, is it?
Nevertheless, the creators of Kotlin made it possible to make it look more familiar in Java. And for this there are several ways.
@JvmField var companionField = "Hello!"
If we apply this annotation to the
companionField
field of our
companionField
object, then when converting bytecode to Java, we see that the
companionField
static field SimpleClassKotlin1 is not private, but public, and the getter and setter for companionField are static in the
Companion
static class. Now we can access
companionField
from Java code in the usual way.
The second way is to specify a
lateinit
modifier for the property of the companion
lateinit
, properties with late initialization. Do not forget that this applies only to var-properties, and its type must be non-null and must not be primitive. Well, do not forget about the rules for dealing with such properties.
lateinit var lateinitField: String
And one more way: we can declare the property of a companion object to be constant, by giving it a const modifier. It is easy to guess that this should be a val-property.
const val myConstant = "CONSTANT"
In each of these cases, the generated Java code will contain the public static field we are used to; in the case of const, this field will also be final. Of course, it should be understood that each of these 3 cases has its own logical purpose, and only the first one is designed specifically for ease of use with Java, the rest receive this “bun” as if in a load.
Separately, it should be noted that the const modifier can be used for properties of objects, companion objects and for package level properties. In the latter case, we get the same as when using the functions of the package level and with the same restrictions. Java code is generated with a static public field in the class, whose name takes into account the name of the file in which we described the constant. In the package there can be only one constant with the specified name.
If we want the function of the companion object to also be transformed into a static method when generating Java code, then for this we need to apply the
@JvmStatic
annotation to this function.
It is also permissible to apply the
@JvmStatic
annotation to properties of companion objects (and just singleton objects). In this case, the property will not turn into a static field, but a static getter and setter to this property will be generated. For a better understanding, let's take a look at this Kotlin-class:
class SimpleClassKotlin1 { companion object{ @JvmStatic fun companionFun (vaue: String){ } @JvmStatic var staticField = 1 } }
In this case, the following references are valid from Java:
int x; SimpleClassKotlin1.companionFun("hello!"); x = SimpleClassKotlin1.getStaticField(); SimpleClassKotlin1.setStaticField(10); SimpleClassKotlin1.Companion.companionFun("hello"); x = SimpleClassKotlin1.Companion.getStaticField(); SimpleClassKotlin1.Companion.setStaticField(10);
From Kotlin such calls are valid:
SimpleClassKotlin1.companionFun("hello!") SimpleClassKotlin1.staticField SimpleClassKotlin1.Companion.companionFun("hello!") SimpleClassKotlin1.Companion.staticField
It is clear that for Java one should use the first 3, and for Kotlin, the first 2. The rest of the calls are only valid.
Now it remains to clarify the latter. How to be with static nested classes? Everything is simple - an analogue of this class in Kotlin is the usual nested class without modifiers:
class SimpleClassKotlin1 { class LooksLikeNestedStatic { } }
After converting bytecode to Java, we see:
public final class SimpleClassKotlin1 { public static final class LooksLikeNestedStatic { } }
Indeed, this is what we need. If we do not want the class to be final, then in the Kotlin code we give it the open modifier. I remembered this just in case.
I think you can summarize. Indeed, in Kotlin itself, as mentioned, there is no static in the form in which we are used to seeing it. But the proposed toolbox allows us to implement all types of statics in the generated Java code. Full compatibility with Java is also provided, and we can directly call static fields and methods of Java classes from Kotlin.
In most cases, static implementation in Kotlin requires a few more lines of code. Perhaps this is one of the few, and maybe even the only case when you need to write more to Kotlin. However, you get used to it quickly.
I think that in projects where Kotlin and Java code are used together, you can be flexible in choosing which language to use. For example, for storing constants, as I personally think, Java is more suitable. But here, as in many other things, one should be guided by both common sense and the rules for writing code in the project.
And at the end of the article here is another such information. Perhaps, in the future, a static modifier will appear in Kotlin, eliminating many issues and making life easier for developers, and the code is shorter. I made this assumption by finding the corresponding text in clause 17 of
Kotlin feature descriptions .
True, this document dates from May 2017, and the end of 2018 is already outside.
I have it all. I think that the topic was disassembled in some detail. Write questions in the comments.