Java to Kotlin Conversion Question. And Answer.
Thursday, July 18, 2019 |Recently, in the #kotlin channel on Freenode, a user asked a question about what was happening to his Java code when using IDEA’s convert-to-Kotlin functionality. He left before anyone had the time to answer, and while he likely doesn’t read my blog, I’m going to answer his question here anyway. :)
Here is his (slightly edited) question:
I use intellij idea to convert a java code to kotlin, but there’s something I don’t understand, https://paste.ubuntu.com/p/NNbRwmR4mG/
I don’t know why kotlin convert the second parameter in memory.subscribeToEvent which is a object to a lambda?
and idea turn f(a,b) in java to f(a)(b) in kotlin
auto currying?
And, just in case the paste disappears, here are its contents:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
java:
tts = new ALTextToSpeech(session);
frontTactilSubscriptionId = 0;
// Subscribe to FrontTactilTouched event,
// create an EventCallback expecting a Float.
frontTactilSubscriptionId = memory.subscribeToEvent(
"FrontTactilTouched", new EventCallback<Float>() {
@Override
public void onEvent(Float arg0)
throws InterruptedException, CallError {
// 1 means the sensor has been pressed
if (arg0 > 0) {
tts.say("ouch!");
}
}
});
kotlin:
tts = ALTextToSpeech(session)
frontTactilSubscriptionId = 0
// Subscribe to FrontTactilTouched event,
// create an EventCallback expecting a Float.
frontTactilSubscriptionId = memory.subscribeToEvent(
"FrontTactilTouched"
) { arg0 ->
// 1 means the sensor has been pressed
if (arg0 > 0) {
tts.say("ouch!")
}
}
To understand why the Kotlin code looks the way it does, it’s important to understand a bit about the Java code. Without
knowing anything about the method memory.subscribeToEvent
, it seems clear that it takes a String
(perhaps an event name?),
and a callback which is, of course, called when the event happens. Incidentally, and not important here, it seems to return
a subscription ID, which I assume one can use to cancel the subscription.
The interesting part of all of this is that second parameter, EventCallBack<T>
. What the Java code is doing is creating an
anonymous instance of the interface and passing it directly to the method. What’s interesting about the interface is that it
is what is known as a Single Abstract Method interface, meaning it has… wait for it… only one abstract method. In Java,
lambdas are actually implemented internally, if I recall correctly, as instances of SAMs, often done silently by the compiler.
This code, then, could be written like this:
1
2
3
4
5
6
7
frontTactilSubscriptionId = memory.subscribeToEvent(
"FrontTactilTouched",
(Float arg0) -> {
if (arg0 > 0) {
tts.say("ouch!");
}
});
If memory serves, the compiler is smart enough to know that the method takes an EventCallback
, and sees that the lambda requires a single
Float
, which happens to match the single abstract method of the interface, so this lambda-ized version of the code is
magically converted to the non-lambda version above in the bytecode, and life moves on.
Now, when we move to Kotlin, the converter code is also smart enough to recognize the SAM in the original Java code, so it converts that to a lambda. It goes a step further, though, and makes the code more idiomatic: in Kotlin, if the last parameter to a method is a lambda, the compiler will allow you to specify that outside the parenthesis in your calling code. This code, then:
1
2
3
4
5
6
7
8
9
frontTactilSubscriptionId = memory.subscribeToEvent(
"FrontTactilTouched",
{ arg0 ->
// 1 means the sensor has been pressed
if (arg0 > 0) {
tts.say("ouch!")
}
)
}
is functionally equivalent to this:
1
2
3
4
5
6
frontTactilSubscriptionId = memory.subscribeToEvent("FrontTactilTouched") { arg0 ->
// 1 means the sensor has been pressed
if (arg0 > 0) {
tts.say("ouch!")
}
}
And there you go. That’s my take on what’s going with that conversion. I hope you find that helpful. And accurate. :) If I missed something, hit the comments below and let’s talk.