web-dev-qa-db-fra.com

Pourquoi dois-je renvoyer Unit.INSTANCE lors de l'implémentation en Java d'une fonction Kotlin qui renvoie une unité?

Si j'ai une fonction Kotlin

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

et je veux appeler f depuis Java, je dois le faire comme ceci:

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

qui a l'air très moche. Pourquoi ne puis-je pas simplement l'écrire comme f(i -> dosomething());, puisque Unit en Kotlin est équivalent à void en Java?

21

Unit en Kotlin est généralement équivalent à void en Java, mais uniquement lorsque les règles de la JVM le permettent.

Les types fonctionnels dans Kotlin sont représentés par des interfaces telles que:

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

Lorsque vous déclarez (Int) -> Unit, du point de vue de Java, cela équivaut à Function<Integer, Unit>. C'est pourquoi vous devez renvoyer une valeur. Pour contourner ce problème, Java comporte deux interfaces distinctes, Consumer<T> et Function<T, R>, lorsque vous n’avez pas/n’ont pas de valeur de retour.

Les concepteurs de Kotlin ont décidé d’abandonner la duplication d’interfaces fonctionnelles au profit du compilateur "magique". Si vous déclarez un lambda dans Kotlin, vous n'avez pas à renvoyer de valeur car le compilateur en insère une pour vous.

Pour vous simplifier un peu la vie, vous pouvez écrire une méthode d'assistance qui enveloppe un Consumer<T> dans un Function1<T, Unit>:

public class FunctionalUtils {
    public static <T> Function1<T, Unit> fromConsumer(Consumer<T> callable) {
        return t -> {
            callable.accept(t);
            return Unit.INSTANCE;
        };
    }
}

Usage:

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

Anecdote: le traitement spécial de Unit par le compilateur Kotlin est la raison pour laquelle vous pouvez écrire du code tel que:

fun foo() {
    return Unit
}

ou

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

Les deux méthodes ont le type de retour void dans le bytecode généré, mais le compilateur est suffisamment intelligent pour comprendre cela et vous permettre d'utiliser les instructions/expressions de retour de toute façon.

31
Kirill Rakhman

J'utilise cette approche pour Kotlin et Java. Les méthodes de MyKotlinClass que vous verrez en Java, tandis que dans Kotlin, vous verrez les deux méthodes (méthode de classe + fonction d'extension) .

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) })
}
0
ultraon