I have Kotlin interface
interface FileSystem {
suspend fun getName(path: Path): List<String>
}
How I can call it from Java? What is
Continuation <? super List<String>>
I have Kotlin interface
interface FileSystem {
suspend fun getName(path: Path): List<String>
}
How I can call it from Java? What is
Continuation <? super List<String>>
Kotlin implements coroutines using a mix of the regular stack-based calling convention and the continuation-passing style (CPS). To achieve that, it performs a CPS transformation on all suspend fun
s by adding an implicit parameter, an object you can use to continue the program from the place where the function was called. That's how Kotlin manages to pull off the trick to suspend the execution within your function's body: it extracts the continuation object, saves it somewhere, and then makes your function return (without yet having produced its value). Later on it can achieve the effect of jumping into the middle of your function body by invoking the continuation object.
The continuation is basically a callback object, just like those familiar from async Java APIs. Instead of returning its result, the suspendable function passes its result to the continuation. To call a suspend fun
from java, you'll have to create such a callback. Here's an example:
Continuation<List<String>> myCont = new Continuation<List<String>>() {
@Override public void resume(List<String> result) {
System.out.println("Result of getName is " + result);
}
@Override public void resumeWithException(Throwable throwable) {
throwable.printStackTrace();
}
@NotNull @Override public CoroutineContext getContext() {
return Unconfined.INSTANCE;
}
};
NOTE: The above works only with experimental coroutines. In the actually released API there's just one resumption method:
resumeWith(result: Result<T>)
whereResult
is a discriminated union of the result type and theinternal class Failure
, which makes it unaccessible from Java.
Let's also create a mock implementation of the FileSystem
interface:
class MockFs : FileSystem {
override suspend fun getName(path: Path): List<String> {
suspendCoroutine<Unit> {
println("getName suspended")
}
println("getName resumed")
return listOf("usr", "opt")
}
}
Now we're ready to call it from Java:
Object result = new MockFs().getName(Paths.get(""), myCont);
System.out.println("getName returned " + result);
It prints
getName suspended
getName returned
kotlin.coroutines.experimental.intrinsics.CoroutineSuspendedMarker@6ce253f1
getName()
returned a special marker object that signals the function got suspended. The function will pass its actual result to our callback, once it resumes.
Let us now improve MockFs
so we can get access to the continuation:
class MockFs : FileSystem {
var continuation : Continuation<Unit>? = null
override suspend fun getName(path: Path): List<String> {
suspendCoroutine<Unit> {
continuation = it
println("getName suspended")
}
println("getName resumed")
return listOf("usr", "opt")
}
}
Now we'll be able to manually resume the continuation. We can use this code:
MockFs mockFs = new MockFs();
mockFs.getName(Paths.get(""), myCont);
mockFs.getContinuation().resume(Unit.INSTANCE);
This will print
getName suspended
getName resumed
Result of getName is [usr, opt]
In real life a suspendable function will use some mechanism to get itself resumed when the result becomes available. For example, if it's a wrapper around some async API call, it will register a callback. When the async API invokes the callback, it will in turn invoke our continuation. You shouldn't need to manually resume it like we did in our mock code.
A suspend fun
also has the option to just return its result directly. For example, with this MockFs
code
class MockFs : FileSystem {
override suspend fun getName(path: Path) = listOf("usr", "opt")
}
in Java we can just say
System.out.println(new MockFs().getName(Paths.get(""), myCont));
and it will print [usr, opt]
. We could even have passed in an empty implementation of Continuation
.
The most demanding case happens when you don't know in advance whether the function will suspend itself or not. In such a case a good approach is to write the following at the call site:
Object retVal = mockFs.getName(Paths.get(""), myCont);
if (retVal != IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
myCont.resume((List<String>) retVal);
}
Otherwise you'll have to duplicate the code that handles the function's result.