Call Kotlin high-order function from Java?

Viewed 393

Let's say that we have a callback in Kotlin:

object Utils {
internal var callback: (() -> Unit)? = null
}

And then we call that callback from a Java class :

Utils.INSTANCE.setCallback$app_debug(new Function0<Unit>() {
        @Override
        public Unit invoke() {
            return null;
       }
    });

So i have to questions :

  1. How can i convert the Unit to void because i'm forced to return a value ?
  2. How can i change the name of the callback setter than setCallback$app_debug ?
4 Answers

The Java naming issue is there because Kotlin compiler really generates getters and setters for vars. Though it's possible to instruct the compiler to expose the callback as a field - just mark it with @JvmField annotation. Java would then treat it just like a regular field.

I also believe if you have to call Kotlin callbacks from Java, you would be better off slightly changing the approach. Instead of declaring the callback as callback: (() -> Unit)? I would rather make it callback: Runnable?.

It will slightly change the way you set the callback.

object Utils {
    @JvmField
    internal var callback: Runnable? = null

    fun main() {
        callback = Runnable { println("Java Runnable") }
    }
}

And will significantly improve the way you interact with it from java

Utils.callback = (() -> System.out.println("Hello!")); 

Workaround

Depending on your preferences it's possible to implement a workaround which works specifically with java interfaces:

// on kotlin side
object Utils {
    internal var callback: (() -> Unit)? = null

    @JvmStatic
    fun setCallback(cb: Runnable) {
        callback = { cb }
    }
}

// on java side

Utils.setCallback(new Runnable() {
    @Override
    public void run() {
        System.out.println("42");
    }
});

Utils.setCallback(() -> {
    System.out.println("42");
});

Side note

You don't need to use INSTANCE if symbol is annotated as @JvmStatic:

// kotlin
object Utils {
    @JvmStatic
    internal var callback: (() -> Unit)? = null
}

// java
Utils.setCallback(...)

Since nobody answered this part:

How can i change the name of the callback setter than setCallback$app_debug ?

By not using internal. Since Java doesn't have internal access qualifier, it has to be public in JVM bytecode, but the name is changed exactly to prevent "accidentally" calling it. If you do want to keep it internal for Kotlin, you can use @JvmName:

@get:JvmName("getCallback")
@set:JvmName("setCallback")
internal var callback: (() -> Unit)? = null

If a function returns Unit, you should return Unit.INSTANCE
For different names, try using @JvmName annotation or @JvmField then you will be able to access this variable as ordinary field rather than auto-generated getter/setter.

Related