如何为Java调用者声明返回类型为“ void”的Kotlin Lambda?

时间:2018-12-06 14:07:29

标签: java lambda kotlin

我有一个完全用Kotlin编写的库,包括其公共API。现在,该库的用户使用Java,这里的问题是返回类型为Unit的Kotlin Lambdas没有被编译为返回类型void。结果是Java端必须始终为有效的Unit.INSTANCE方法返回void。可以避免这种情况吗?

示例:

Kotlin Lambda

interface Foo{
  fun bar(x:(String)->Unit)
}

Java调用

public void call(){
   foo.bar(this::processString)
}

//the return type should rather be void instead of Unit
public Unit processString(String s){  
    return Unit.INSTANCE 
    // ^^ implementations should not be forced to return anything 
 }

是否可以以不同的方式声明Kotlin Lambda,以便编译器生成void返回类型?

另请参阅How to declare a Kotlin function with return type 'void' for a java caller?

2 个答案:

答案 0 :(得分:5)

我对此没有真正的答案,但我会分享在需要从Java访问此类Kotlin代码(或我想到的情况)的情况下所做的事情。

基本上,这取决于您真正想要触摸/增强哪一边才能获得所需的内容。

增强Kotlin代码以支持Java等效项:

interface Foo {
  fun bar(x : (String) -> Unit)

  /* the following is only here for Java */
  @JvmDefault // this requires that you add -Xjvm-default=enable to your compiler flags!
  fun bar(x:Consumer<String>) = bar(x::accept)
}

这有一些缺点:Consumer方法在Kotlin中也是可见的,因此也可以从Kotlin中调用。不用说,您需要复制接口中的所有功能,因此整个Kotlin接口只会变得更加肿。但是:它在双方都可以按您期望的方式工作。 Java调用Consumer-variant,Kotlin调用(String) -> Unit-variant ...希望;-)实际上只是演示了一些调用:

// from Java:
..bar(s -> { System.out.println(s); })
// however, method references might not work that easily or not without a workaround...
..bar((Consumer<String>) System.out::println); // not nice... @JvmName("kotlinsBar") to the rescue? well... that will just get more and more ugly ;-)

// from Kotlin:
..bar(Consumer(::println)) // or: ..bar(Consumer { println(it) })
..bar(::println)           // or: ..bar { println(it) } // whatever you prefer...

话虽如此,另一个变种是添加帮助程序方法,该方法实际上有助于从Java轻松调用Kotlin函数,例如如下:

fun <T> `$`(consumer: Consumer<T>): (T) -> Unit = consumer::accept

可能永远不会从Kotlin调用它(因为编写带$的反引号已经很麻烦了),或者如果您不想膨胀您的Kotlin代码,只需在Java中添加这样的方法即可,但实际上并没有看起来苗条:

static <T> Function1<T, Unit> $(Consumer<T> consumer) {
    return t -> {
        consumer.accept(t);
        return Unit.INSTANCE;
    };
}

对这些方法的调用都相同:

..bar($(s -> /* do something with s */)) // where bar(x : (String) -> Unit)

对于需要解决的问题,我只是返回了Unit.INSTANCEnull,但是如果我有更多的调用方法,我可能会选择第二种($(...))方法。在最佳情况下,我只需要提供(生成?;-))等效项并在多个项目中使用它们,而仅在Java接口中提供default变体可能会需要更多的工作,甚至可能使某些人感到困惑...

最后:不...我不知道有任何选项可以让您在Kotlin的void返回功能接口中拥有类似Unit功能的接口(/消费者)

答案 1 :(得分:2)

实际上没有必要在 Roland 的回答中添加 $ 符号。
只需一个方法名,即可在kotlin和java中直接获得良好的编码体验。

这是你需要做的(实际上和罗兰的回答一样,但我的示例代码供你参考):

  1. 为java定义一个接口
interface `Consumer$`<T> : Function1<T, Unit> {
    fun accept(t: T)

    @JvmDefault
    override fun invoke(t: T) {
        accept(t)
    }
}
// this interface is a FunctionalInterface
// the `$` prevents importing collision with java.util.functional.Consumer
  1. 在您的类/接口中,添加一个供 java 调用的函数
class Foo {
    fun bar(f: (String) -> Unit) {}
    fun bar(f: `Consumer$`<String>) {
        bar(f as (String)->Unit)
                      // because that the `Consumer$` extends Function1
                      // it can be directly passed as a kotlin function
    }
}
  1. 在 kotlin 代码中,您可以编写
foo.bar { it.xxx }
  1. 在java代码中,你可以编写
foo.bar(it -> it.xxx)
// as well as
foo.bar(System.out::println)

javac知道引用方法的返回类型不是Unit,所以会调用Consumer$方法。
kotlin 编译器不会检查 FunctionX 的继承,所以会调用 (String)->Unit 方法。