在Java中实现返回Unit的Kotlin函数时,为什么需要返回Unit.INSTANCE?

72

如果我有一个Kotlin函数

fun f(cb: (Int) -> Unit)

如果我想从Java中调用f,我需要这样做:

f(i -> {
     dosomething();
     return Unit.INSTANCE;
});

看起来非常丑陋。为什么我不能像这样写呢 f(i -> dosomething());,因为 Kotlin 中的 Unit 相当于 Java 中的 void


1
这很丑,让我甚至不想使用 Kotlin。随着我越来越多地使用这种语言,我一遍又一遍地看到这些奇怪的东西。 - user2836797
14
这是因为需要与Java进行交互。Kotlin本身很好,相当优雅。 - Randy Sugianto 'Yuku'
@It'sYourAppLLC 只有在 Java 代码中使用 Kotlin 代码时才会看到这个。修复它的简单方法是消除 Java 代码的使用,完全使用 Kotlin 编写。现在我们有了 Kotlin,没有更多使用 Java 的理由了。以我个人的看法。 - mojmir.novak
2个回答

73

Kotlin中的Unit在大多数情况下等同于Java中的void,但仅当JVM的规则允许时。

Kotlin中的函数类型由诸如以下接口表示:

public interface Function1<in P1, out R> : Function<R> {
    /** Invokes the function with the specified argument. */
    public operator fun invoke(p1: P1): R
}

当你声明 (Int) -> Unit 时,从Java的角度来看,这相当于 Function<Integer, Unit>。这就是为令你必须返回一个值的原因。为了解决这个问题,在Java中有两个独立的接口 Consumer<T>Function<T, R>,分别用于没有/有返回值的情况。
Kotlin的设计者决定放弃函数接口的重复,并依靠编译器的“魔法”。如果你在Kotlin中声明一个lambda表达式,你不需要返回一个值,因为编译器会为你插入一个值。
为了让你的生活更加轻松,你可以编写一个帮助方法,将一个Consumer<T>包装在Function1<T, Unit>中:
public class FunctionalUtils {
    public static <T> Function1<T, Unit> fromConsumer(Consumer<T> callable) {
        return t -> {
            callable.accept(t);
            return Unit.INSTANCE;
        };
    }
}

使用方法:

f(fromConsumer(integer -> doSomething()));

有趣的事实:Kotlin编译器对Unit的特殊处理是您可以编写以下代码的原因:

fun foo() {
    return Unit
}

或者

fun bar() = println("Hello World")

两种方法在生成的字节码中的返回类型都是void,但编译器足够聪明以解析出这一点,并允许您使用返回语句/表达式。

11
编译器无法在没有真实的Unit实例的情况下理解最后一个示例,这让人难以置信。 - voddan
1
不幸的是,java.util.function.* 只在 API 23 及以上版本可用。请参考此答案 here,并使用 android-retrostreams 来支持 API 24 及以下版本。 - mochadwi

1

我在Kotlin和Java中使用这种方法。你会在Java中看到MyKotlinClass的方法,在Kotlin中你会看到两种方法(类方法+扩展函数)。

MyKotlinClass {

  //Method to use in Java, but not restricted to use in Kotlin.
    fun f(cb: Consumer<Int>) { //Java8 Consumer, or any custom with the same interface
      int i = getYourInt()
      cb.accept(i)
    }
}

//Extension for Kotlin. It will be used in Kotlin.
fun MyKotlinClass.f(cb: (Int) -> Unit) {
    f(Consumer { cb(it) })
}

1
虽然看起来很优雅,但在运行时创建了两个不同的匿名对象,这并不好。 - Animesh Sahu

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接