Java interop: Unit closures required to return kotlin.Unit

Given this function:

object Tmp {
    @JvmStatic fun hello(f: () -> Unit) {
    }
}

The following Java code doesn’t compile

public class Hello {
    public void asd() {
        Tmp.hello(() -> {
        });
    }
}

as it requires the closure to return Unit.INSTANCE.
Suggestion: Add @JvmVoid annotation to denote Units that should actually behave as void

10 Likes

I don’t understand why Unit is better than void or Void…

Someone can help me?

Unit is a type (unlike void) and has a value (unlike Void). This enable uniform treatment of Unit when it comes to generic classes. I.e. we don’t need another two types: a function that returns something and a function that returns void. It’s all one type: a function that returns something tat may be Unit.

Introducing void would pose many problems in areas like type inference, compositionality of any sort of functions, etc

2 Likes

@abreslav Could you explain in which case it’s better to use Nothing over Unit ?

Thanks for response,
those are good points.
I’m interested to know how you will fix this issue.

Nothing is for a function/method that never returns,
i.e. System.exit() should return Nothing.

Also “throw” statement returns nothing.

All statements after a Nothing one are unreachable.

I’m not suggesting introducing void as a kotlin type, but rather annotating Unit for better Java interop. There is a straightforward translation from void to Unit which the compiler could do:

If void is covariant:

fun f(): @JvmVoid Unit {..}

would compile to two functions, one for kotlin, one for java:

public void f_kotlin(): Unit {
  ..
}
public void f_java() {
  f_kotlin();
}

And if it’s contravariant the java version would just wrap void functions and pass in Unit.INSTANCE automatically.

6 Likes

This functionality is also required to maintain compatibility with code that inspects the return type of a method and will fail if it is not void. For example, in the Arquillian testing suite, the org.jboss.arquillian.junit.Arquillian.validatePublicVoidNoArgMethods() method checks to make sure that a test function annotated with @BeforeClass returns void. Without some way to generate the correct signature, Kotlin can’t be used with these libraries.

Hello, is there any solution so far to pass () -> {} in Java to Kotlin function as an argument, which is declared as () -> Unit?

5 Likes

Any updates on this? Learning Kotlin second day and facing this already. The best I come up with is
Kotlin:

fun created(callback: (s: Subscriber) -> Unit)  {
    callback(this)
}

Java (ugly return null):

subscriber.created(s -> {
    System.out.println("User " + s + " was created");
    return null;
});

Any better solution for this?

Better no.
Advise which is not like by kotlin itself:
If you want to be java-compatible, declare your lambdas using java-functional interfaces.
Kotlin has fun interfaces themselves, but I didn’t look into them yet

for everyone stumbling over this problem, i use following workaround.

My kotlin function is this:

fun justSomeTest(f:()->Unit){}

To call it in Java, it would look like this:

bla.justSomeTest(()->{return null;});

But with adding an overload in kotlin with the Java Interfaces like this:

fun justSomeTest(f:Runnable)=justSomeTest { f.run() }

the java code becomes the following:

bla.justSomeTest(()->{});

Obviously this also works with the Java interfaces Function, Consumer, Supplier etc the same way

Might be a little to the party, I ended up using interfaces to solve this.

for one or two usages this is an overkill, bt if you find yourself return Unit.INSTANCE over and over again, do this:

  1. define an interface for your callback as YourInerface
  2. define your kotlin function with the callback argument being of type YourInerface
  3. add an extension fun with the callback argument being of pure-kotlin-function type
    Android Studio (and other IDEs) don’t auto-complete kotlin extensions in java files, so the kotlin variant is excluded from the auto-complete suggestions.

demo:

data class User(...)

// 1. interface
interface OnGotUser { fun onGotUser(user: User) }

class UserGetter {
  // 2. fun with callback of type interface
  fun retrieveUser(callback: OnGotUser){
    val user: User = getUserUsingFancyStuff() // get it somehow 
    callback.onGotUser(user)
  }
}

// 3. extension with callback of type pure-function
// if the Interface OnGotUser is defined in a java file, you can use SAM constructors and do this:
fun UserGetter.retrieveUser(callback: (User)->Unit) 
  = retrieveUser(OnGotUser { callback(it) }) 

// if the interface OnGotUser is defined in a kotlin file, it looks a little bit worse
// [see kotlin bug](https://discuss.kotlinlang.org/t/sam-conversion-on-kotlin-interfaces/1433):
fun UserGetter.retrieveUser(callback: (User)->Unit) = 
  retrieveUser(object: OnGotUser { 
    override fun onGotUser(user: User) = callback(user)
  })

2 Likes

My workaround: declare the following helpers in kotlin (basically conversion from java type interface to kotlin type interface):

fun voidc(runnable: Runnable) : () -> Unit = runnable::run
fun <X> voidc(consumer : Consumer<X>) : (X) -> Unit = consumer::accept
fun <X, Y> voidc(consumer : BiConsumer<X, Y>) : (X, Y) -> Unit = consumer::accept

In Java, you just then need to wrap lambda with this convertor, e.g.:

subscriber.created(voidc(s -> System.out.println("User " + s + " was created")));

Just a little better than returning nulls.

1 Like