Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Passing lambda instead of interface

Tags:

lambda

kotlin

I have created an interface:

interface ProgressListener {
    fun transferred(bytesUploaded: Long)
}

but can use it only as an anonymous class, not lambda

dataManager.createAndSubmitSendIt(title, message,
object : ProgressListener {
    override fun transferred(bytesUploaded: Long) {
        System.out.println(bytesUploaded.toString())
    }
})

I think it should be a possibility to replace it by lambda:

dataManager.createAndSubmitSendIt(title, message, {System.out.println(it.toString())})

But I am getting error: Type mismatch; required - ProgressListener, found - () -> Unit?

What am I doing wrong?

like image 244
Viktor Sinelnikov Avatar asked Apr 18 '17 10:04

Viktor Sinelnikov


People also ask

Can we use lambda expression without interface?

Surely lambda expression can be one-time used as your commented code does, but when it comes to passing lambda expression as parameter to mimic function callback, functional interface is a must because in that case the variable data type is the functional interface.

Is lambda expression only for interface?

No, all the lambda expressions in this code implement the BiFunction<Integer, Integer, Integer> function interface. The body of the lambda expressions is allowed to call methods of the MathOperation class. It doesn't have to refer only to methods of a functional interface.

Is lambda an interface or function?

A lambda expression (lambda) is a short-form replacement for an anonymous class. Lambdas simplify the use of interfaces that declare single abstract methods. Such interfaces are known as functional interfaces. A functional interface can define as many default and static methods as it requires.

Why are lambda expressions used for only functional interfaces?

Because the lambda expression provides a body to only a single abstract method.


3 Answers

As @zsmb13 said, SAM conversions are only supported for Java interfaces.

You could create an extension function to make it work though:

// Assuming the type of dataManager is DataManager. fun DataManager.createAndSubmitSendIt(title: String,                                        message: String,                                        progressListener: (Long) -> Unit) {     createAndSubmitSendIt(title, message,         object : ProgressListener {             override fun transferred(bytesUploaded: Long) {                 progressListener(bytesUploaded)             }         }) } 

Edit:

Kotlin 1.4 will bring function interfaces that enables SAM conversions for interfaces defined in Kotlin. This means that you can call your function with a lambda if you define your interface with the fun keyword. Like this:

fun interface ProgressListener {     fun transferred(bytesUploaded: Long) } 
like image 134
marstran Avatar answered Oct 05 '22 00:10

marstran


Kotlin only supports SAM conversions for Java interfaces.

... note that this feature works only for Java interop; since Kotlin has proper function types, automatic conversion of functions into implementations of Kotlin interfaces is unnecessary and therefore unsupported.

-- Official documentation

If you want to use a lambda in the parameter, make your function take a function parameter instead of an interface. (For now at least. Supporting SAM conversions for Kotlin interfaces is an ongoing discussion, it was one of the possible future features at the Kotlin 1.1 live stream.)

like image 28
zsmb13 Avatar answered Oct 05 '22 02:10

zsmb13


UPDATED: September 7th, 2020

As the Kotlin documentation for the Kotlin 1.4 release points out:

Before Kotlin 1.4.0, you could apply SAM (Single Abstract Method) conversions only when working with Java methods and Java interfaces from Kotlin. From now on, you can use SAM conversions for Kotlin interfaces as well. To do so, mark a Kotlin interface explicitly as functional with the fun modifier.

fun interface Operation1 {
    operator fun invoke(x: String): String
}

fun interface Operation2 {
    fun doSomething(x: Int): String
}

val operation1 = Operation1 { "$it world!" }
val operation2 = Operation2 { "$it world!" }

fun main() {
    // Usage: First sample.
    println(operation1("Hello"))
    println(operation2.doSomething(0))
    // Usage: Second sample.
    println(Operation1 { "$it world!" }("Hello"))
    println(Operation2 { "$it!" }.doSomething(0))
}

You can read more about functional interfaces here.

Previous solution:

Declare a typealias, inject it somewhere and invoke it later on:

internal typealias WhateverListener = (String) -> Unit

and then we inject that typealias to our class:

class Gallery constructor(private val whateverListener: WhateverListener) {
    
    ...
    
    galleryItemClickListener.invoke("hello")

    ...
}

so we have our lambda:

val gallery = Gallery { appNavigator.openVideoPlayer(it) }

Credits to my colleague Joel Pedraza, who showed me the trick while trying to find a solution <3.

NOTE: Check out the Kotlin documentation in case you want to know when to use either functional interfaces (lambdas) or type aliases.

like image 31
cesards Avatar answered Oct 05 '22 02:10

cesards