Coming Up for Air

Java to Kotlin Conversion Question. And Answer.

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:

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:

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:

frontTactilSubscriptionId = memory.subscribeToEvent(
        "FrontTactilTouched",
        { arg0 ->
        // 1 means the sensor has been pressed
        if (arg0 > 0) {
            tts.say("ouch!")
        }
    )
}

is functionally equivalent to this:

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.

tags: Kotlin

Quotes

Sample quote

Quote source