【问题标题】:Call Kotlin suspend function in Java class在 Java 类中调用 Kotlin 挂起函数
【发布时间】:2018-10-18 08:12:14
【问题描述】:

假设我们有以下挂起函数:

suspend fun doSomething(): List<MyClass> { ... }

如果我想在我现有的 Java 类之一中调用此函数(我现在无法转换为 Kotlin)并获取它的返回值,我必须提供一个 Continuation&lt;? super List&lt;MyClass&gt;&gt; 作为它的参数(显然) .

我的问题是,我该如何实施。特别是它的getContext getter。

【问题讨论】:

  • 我会尽一切可能避免这样做;我不希望它非常有效地工作。例如,您可以添加另一个 Kotlin 函数来启动您认为合适的协程,这不是挂起的乐趣,然后从 Java 中调用它。
  • 我曾经有Java code 设法创建Continuation 的实现并调用suspend fun,但在Kotlin 1.3 中Continuation 声明resumeWith(Result),其中Result 是结果和 internal class Failure 的可区分联合,除了使用反射访问 Kotlin 实现中的私有成员之外,没有办法从 Java 提供它。

标签: java kotlin kotlin-coroutines


【解决方案1】:

首先,将org.jetbrains.kotlinx:kotlinx-coroutines-jdk8 模块添加到您的依赖项中。在您的 Kotlin 文件中定义以下与 Java 编写异步 API 风格相对应的异步函数:

fun doSomethingAsync(): CompletableFuture<List<MyClass>> =
    GlobalScope.future { doSomething() }

现在使用 Java 中的doSomethingAsync,就像在 Java 世界中使用其他异步 API 一样。

【讨论】:

  • 对于 Gradle 新手用户(比如我)来说仅供参考 - 您需要将版本添加到依赖项中,例如:implementation "org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:1.0.1"
  • 不适用于 Android 6.0。错误:java.lang.NoClassDefFoundError:解析失败:Ljava/util/concurrent/CompletableFuture;
  • 您将其表示为任何其他未来类型,并在您的 Java 代码中使用。我们内置支持转换为 CompletableFuture、ListenableFuture、Rx Single、Reactor Mono。你也可以自己写。
  • 注意:这需要 API 级别 24 或以上。
  • “现在在 Java 中使用 doSomethingAsync 的方式与在 Java 世界中使用其他异步 API 的方式相同。”怎么样???
【解决方案2】:

如果你不想使用org.jetbrains.kotlinx:kotlinx-coroutines-jdk8,我有一个新想法。

在您的 kotlin 项目中编写以下代码。

    @JvmOverloads
    fun <R> getContinuation(onFinished: BiConsumer<R?, Throwable?>, dispatcher: CoroutineDispatcher = Dispatchers.Default): Continuation<R> {
        return object : Continuation<R> {
            override val context: CoroutineContext
                get() = dispatcher

            override fun resumeWith(result: Result<R>) {
                onFinished.accept(result.getOrNull(), result.exceptionOrNull())
            }
        }
    }

我把它写在我的Coroutines 类中

然后你可以调用你的挂起函数:

            Coroutines coroutines = new Coroutines();
            UserUtils.INSTANCE.login("user", "pass", coroutines.getContinuation(
                    (tokenResult, throwable) -> {
                        System.out.println("Coroutines finished");
                        System.out.println("Result: " + tokenResult);
                        System.out.println("Exception: " + throwable);
                    }
            ));

login() 函数是一个挂起函数。
suspend fun login(username: String, password: String): TokenResult

对于您的代码,您可以:

doSomething(getContinuation((result, throwable) -> { 
       //TODO
}));

【讨论】:

  • 太棒了!!!这是在 java 中调用协程以实现互操作性的一种解决方法
  • 就像 Roman 的回答一样,这也需要 API 24 或更高版本
  • @Samuel 或许你可以用自己的接口和匿名类替换 BiConsumer 和 Lambda,使其兼容旧的 Android SDK(未测试)
【解决方案3】:

对于协程 1.3.0 使用这个:

BuildersKt.launch(GlobalScope.INSTANCE,
                Dispatchers.getMain(),//context to be ran on
                CoroutineStart.DEFAULT,
                (coroutineScope, continuation) -> suspendFunction(arguments)
        );

对于java

BuildersKt.launch(
        GlobalScope.INSTANCE,
        Dispatchers.getMain(),//context to be ran on
        CoroutineStart.DEFAULT,
        new Function2<CoroutineScope, Continuation<? super Unit>, Unit/*or your return type here*/>() {
            @Override
            public Unit/*or your return type here*/ invoke(CoroutineScope coroutineScope, Continuation<? super Unit> continuation) {
                //do what you want
                return Unit.INSTANCE; //or something with the defined type
            }
        }
);

我的 gradle 文件:

implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.3.50"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"

Kotlin 使用静态类作为扩展函数,launch 是扩展函数,所以在 BuildersKt 中定义。第一个参数是扩展函数的目标,其余的是扩展函数的参数。

【讨论】:

  • 这种方法没用,因为您无法恢复继续,它需要一个您无法从 Java 访问的内联类。
  • 我不明白你的意思。如果你想使用 Deferred,你可以使用 BuildersKt.async 代替启动,这就是协程的工作方式。
  • // do what you want -- 你不能在这里做你想做的事情,因为你必须提供一个期望以被调用函数的返回类型恢复的延续,但你没有这样的继续,您的答案没有显示如何从 Java 构造它。你从launch 得到的延续只是完成延续,当整个协程完成时你用Unit 调用的延续。
  • @MarkoTopolnik 您可以尝试将 lambda 上的延续作为您的 suspendFunction() 的最后一个参数传递。即,(coroutineScope,continuation)->suspendFunction(arguments,continuation)
猜你喜欢
  • 2020-10-11
  • 2018-06-16
  • 2019-05-30
  • 2021-01-20
  • 2018-12-11
  • 1970-01-01
  • 1970-01-01
  • 2020-01-27
  • 2021-09-03
相关资源
最近更新 更多