Search code examples
javakotlinkotlin-java-interop

How do you make Kotlin static variables and functions for Java?


Since Google made Kotlin a first class language for Android, there has been an increase in questions relating to how to perform certain things in Kotlin, "Java-esque" style. The most common ones are how to make static variables in Kotlin. So how do you make Kotlin static variables and functions?


Solution

  • You can't. Well, at least in a pure Kotlin project.

    Kotlin has no notion of static. The way static works in a Kotlin-Java project is by use of annotations on the Kotlin classes to tell the JVM that the desired variable/function should be exposed as a static to Java classes.

    The following is an example guide for Kotlin-Java static interop (answer originally posted in What is the equivalent of Java static methods in Kotlin?):

    Scenario 1: Creating a static method in Kotlin for Java

    Kotlin

    @file:JvmName("KotlinClass") //This provides a name for this file, so it's not defaulted as [KotlinClassKt] in Java
    package com.frybits
    
    class KotlinClass {
        companion object {
            
            //This annotation tells Java classes to treat this method as if it was a static to [KotlinClass]
            @JvmStatic
            fun foo(): Int = 1
            
            //Without it, you would have to use [KotlinClass.Companion.bar()] to use this method.
            fun bar(): Int = 2
        }
    }
    

    Java

    package com.frybits;
    
    class JavaClass {
    
        void someFunction() {
            println(KotlinClass.foo()); //Prints "1"
            println(KotlinClass.Companion.bar()); //Prints "2". This is the only way to use [bar()] in Java.
            println(KotlinClass.Companion.foo()); //To show that [Companion] is still the holder of the function [foo()]
        }
    
        //Because I'm way to lazy to keep typing [System.out], but I still want this to be compilable.
        void println(Object o) {
            System.out.println(o);
        }
    }
    

    This answer provides more depth than this, and should definitely be referenced for this scenario.


    This next scenario handles creating static fields in Kotlin so that Java doesn't have to keep calling KotlinClass.foo() for those cases where you don't want a static function.

    Scenario 2: Creating a static variable in Kotlin for Java

    Kotlin

    @file:JvmName("KotlinClass") //This provides a name for this file, so it's not defaulted as [KotlinClassKt] in Java
    package com.frybits
    
    class KotlinClass {
    
        companion object {
    
            //This annotation tells Kotlin to not generate the getter/setter functions in Java. Instead, this variable should be accessed directly
            //Also, this is similar to [@JvmStatic], in which it tells Java to treat this as a static variable to [KotlinClass].
            @JvmField
            var foo: Int = 1
    
            //If you want something akin to [final static], and the value is a primitive or a String, you can use the keyword [const] instead
            //No annotation is needed to make this a field of [KotlinClass]. If the declaration is a non-primitive/non-String, use @JvmField instead
            const val dog: Int = 1
    
            //This will be treated as a member of the [Companion] object only. It generates the getter/setters for it.
            var bar: Int = 2
    
            //We can still use [@JvmStatic] for 'var' variables, but it generates getter/setters as functions of KotlinClass
            //If we use 'val' instead, it only generates a getter function
            @JvmStatic
            var cat: Int = 9
        }
    }
    

    Java

    package com.frybits;
    
    class JavaClass {
    
        void someFunction() {
            //Example using @JvmField
            println(KotlinClass.foo); //Prints "1"
            KotlinClass.foo = 3;
    
            //Example using 'const val'
            println(KotlinClass.dog); //Prints "1". Notice the lack of a getter function
    
            //Example of not using either @JvmField, @JvmStatic, or 'const val'
            println(KotlinClass.Companion.getBar()); //Prints "2"
            KotlinClass.Companion.setBar(3); //The setter for [bar]
    
            //Example of using @JvmStatic instead of @JvmField
            println(KotlinClass.getCat());
            KotlinClass.setCat(0);
        }
    
        void println(Object o) {
            System.out.println(o);
        }
    }
    

    One of the great features about Kotlin is that you can create top level functions and variables. This makes it greate to create "classless" lists of constant fields and functions, which in turn can be used as static functions/fields in Java.

    Scenario 3: Accessing top level fields and functions in Kotlin from Java

    Kotlin

    //In this example, the file name is "KSample.kt". If this annotation wasn't provided, all functions and fields would have to accessed
    //using the name [KSampleKt.foo()] to utilize them in Java. Make life easier for yourself, and name this something more simple
    @file:JvmName("KotlinUtils")
    
    package com.frybits
    
    //This can be called from Java as [KotlinUtils.TAG]. This is a final static variable
    const val TAG = "You're it!"
    
    //Since this is a top level variable and not part of a companion object, there's no need to annotate this as "static" to access in Java.
    //However, this can only be utilized using getter/setter functions
    var foo = 1
    
    //This lets us use direct access now
    @JvmField
    var bar = 2
    
    //Since this is calculated at runtime, it can't be a constant, but it is still a final static variable. Can't use "const" here.
    val GENERATED_VAL:Long = "123".toLong()
    
    //Again, no need for @JvmStatic, since this is not part of a companion object
    fun doSomethingAwesome() {
        println("Everything is awesome!")
    }
    

    Java

    package com.frybits;
    
    class JavaClass {
    
        void someFunction() {
    
            println(KotlinUtils.TAG); //Example of printing [TAG]
    
    
            //Example of not using @JvmField.
            println(KotlinUtils.getFoo()); //Prints "1"
            KotlinUtils.setFoo(3);
    
            //Example using @JvmField
            println(KotlinUtils.bar); //Prints "2". Notice the lack of a getter function
            KotlinUtils.bar = 3;
    
            //Since this is a top level variable, no need for annotations to use this
            //But it looks awkward without the @JvmField
            println(KotlinUtils.getGENERATED_VAL());
    
            //This is how accessing a top level function looks like
            KotlinUtils.doSomethingAwesome();
        }
    
        void println(Object o) {
            System.out.println(o);
        }
    }
    

    Another notable mention that can be used in Java as "static" fields are Kotlin object classes. These are zero parameter singleton classes that are instantiated lazily on first use. More information about them can be found here: https://kotlinlang.org/docs/reference/object-declarations.html#object-declarations

    However, to access the singleton, a special INSTANCE object is created, which is just as cumbersome to deal with as Companion is. Here's how to use annotations to give it that clean static feel in Java:

    Scenario 4: Using object classes

    Kotlin

    // There is no more need for the @file:JvmName() annotation. The object class below already handles the proper naming.
    
    //This provides a name for this file, so it's not defaulted as [KotlinClassKt] in Java
    package com.frybits
    
    object KotlinClass { //No need for the 'class' keyword here.
    
        //Direct access to this variable
        const val foo: Int = 1
    
        //Tells Java this can be accessed directly from [KotlinClass]
        @JvmStatic
        var cat: Int = 9
    
        //Just a function that returns the class name
        @JvmStatic
        fun getCustomClassName(): String = this::class.java.simpleName + "boo!"
    
        //Getter/Setter access to this variable, but isn't accessible directly from [KotlinClass]
        var bar: Int = 2
    
        fun someOtherFunction() = "What is 'INSTANCE'?"
    }
    

    Java

    package com.frybits;
    
    class JavaClass {
    
        void someFunction() {
            println(KotlinClass.foo); //Direct read of [foo] in [KotlinClass] singleton
    
            println(KotlinClass.getCat()); //Getter of [cat]
            KotlinClass.setCat(0); //Setter of [cat]
    
            println(KotlinClass.getCustomClassName()); //Example of using a function of this 'object' class
    
            println(KotlinClass.INSTANCE.getBar()); //This is what the singleton would look like without using annotations
            KotlinClass.INSTANCE.setBar(23);
    
            println(KotlinClass.INSTANCE.someOtherFunction()); //Accessing a function in the object class without using annotations
        }
    
        void println(Object o) {
            System.out.println(o);
        }
    }