Search code examples
javakotlinvalue-classkotlin-companionkotlin-inline-class

How to use kotlin value class from java code?


I have kotlin value class:

@JvmInline
value class MyValueClass internal constructor(val rawValue: String) {
    companion object {

        @JvmName("myTestValueClass")
        fun String.testValueClass(): MyValueClass {
            return MyValueClass(
                rawValue = "test",
            )
        }
    }
}

And now I'm trying to use my value class from java code.

MyValueClass test = MyValueClass.Companion.myTestValueClass("test");

But I get error: Required MyValueClass provided String.

Why is this happening? Please, help me.


Solution

  • testValueClass returns a String because that is exactly the point of value classes - they are inlined.

    The whole point of value classes is that they are light-weight wrappers that has very little overhead. Kotlin tries very hard to not create instances of value classes.

    In the JVM bytecode, value classes are replaced with the types they wrap, whenever possible, so that's why testValueClass returns a String as far as Java is concerned. The generated bytecode for testValueClass would be equivalent to return "test";.

    In Kotlin, you can still use members declared in MyValueClass even without creating an instance of MyValueClass:

    @JvmInline
    value class MyValueClass internal constructor(val rawValue: String) {
        fun foo() {}
    
        // this actually generates a synthetic static method that does the same thing as foo:
        // public static void fooImpl(String str) { }
        // fooImpl is for demonstration purposes only, not the actual name of the method. 
    }
    
    fun main(args: Array<String>) {
        // this doesn't actually create an instance of MyValueClass under the hood
        MyValueClass("Foo").foo()
        // this is translated to a static method call:
        // MyValueClass.fooImpl("Foo");
    }
    

    Recommended reading: Inline value classes in the Kotlin documentation.

    In Java, however, there is no such language feature. If you want to call the foo method, you have to introduce an interface in Kotlin through which foo can be called, and provide a method that returns that interface. By using an interface type as the return type, you force an instance of the value class to be created.

    interface MyValueClassInterface {
        fun foo()
    }
    
    @JvmInline
    value class MyValueClass(val rawValue: String): MyValueClassInterface {
        companion object {
            // ...
    
            @JvmStatic
            @JvmName("box")
            fun box(x: MyValueClass) = x as MyValueClassInterface
        }
    
        override fun foo() {
            // ...
        }
    }
    
    MyValueClassInterface bar = MyValueClass.box(
            MyValueClass.Companion.myTestValueClass("Foo")
    );
    // now you can do
    bar.foo();
    

    Of course, you can also write a separate version of myTestValueClass that directly returns the interface.