7

I want to implement a functional kotlin interface (interface with a single abstract method) as a kotlin lambda. How to do that?

Kotlin interface

@FunctionalInterface
interface Foo{
  fun bar(input: String): String 
}

Kotlin implementation .

fun createFoo(): Foo {
   return { input: String -> "Hello $input!" }
}

↑ doesn't compile ↑

It has to be implemented as object, which is ugly as hell.

fun createFoo() = 
     object : Foo{
            override fun bar(input: String)= "Hello $input"
     }

EDIT: corrected my sample interface from java to kotlin

2
  • 1
    SAM conversion for Kotlin interfaces is not allowed by design. Quoting the documentation: "note that this feature [SAM conversion] 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." Commented Nov 5, 2019 at 15:50
  • 1
    Here is a related issue regarding this: KT-7770 Commented Nov 5, 2019 at 15:53

6 Answers 6

6

Just add the fun keyword to your interface declaration:

fun interface Foo {
    fun bar(input: String): String 
}

It is the notation of functional interfaces in Kotlin (instead of @FunctionalInterface annotation in Java).

Now you can implement it like this:

Foo { input: String -> "Hello $input!" }

See more: https://kotlinlang.org/docs/fun-interfaces.html

Sign up to request clarification or add additional context in comments.

Comments

4

since Kotlin v1.4

SAM conversion will be supported with version 1.4, with a new type inference algorithm.

See:


before Kotlin v1.4

It works if the companion object implements the invoke function taking a lambda.

Kotlin interface

interface Foo{
  fun bar(input: String): String

   companion object { 
      inline operator fun invoke(crossinline function: (String) -> String) =
                object : Foo{
                    override fun bar(input: String) = function(input)
                } 
   } 
}

Kotlin implementation

fun createFoo()= Foo { input: String -> "Hello $input!" }

Functional/SAM interfaces defined in kotlin can't be implemented as Kotlin lambdas by design, see KT-7770.

In Kotlin an functional / SAM interface is considered as an anti-pattern, a function type should be declared instead: (String)->String. The function type can be expressed as typealias to make it look and feel like an interface: typealias Foo=(String)->String.

Note: The typealias is not visible in Java code only in Kotlin!

Comments

2

I want to implement a functional kotlin interface (interface with a single abstract method) as a kotlin lambda. How to do that?

Can't

It has to be implemented as object, which is ugly as hell.

Indeed.


You have two options:

1.) use typealias

typealias Foo = (String) -> String

fun createFoo(): Foo = { "Hello $it!" }

2.) depending on your API, you can define an extension function that receives the functional type (String) -> String as a crossinline argument, then invokes it inside a object: __ block. That way, you can hide the object: in a given function, and externally still be able to call it with a lambda argument. Doesn't seem applicable in this case, though.

Comments

1

I don't think there is a language level option to do this, but you can abstract the "ugly" code into a helper method so its easier to read where the business logic is actually needed:

Helper Method

fun Foo(body: (String) -> String) = object : Foo{
  override fun bar(input: String)= body(input)
}

Business Code

fun createFoo():Foo {
  return Foo {input:String -> "Hello $input!"}
}

2 Comments

I updated my question, the interface is defined in kotlin not java.
@Chriss, this solution works fine with interfaces that are defined in Kotlin. But you can make it a little bit better by adding an inline modifier to Foo function: inline fun Foo(crossinline body: (String) -> String) = object : Foo {...}. It will prevent creating additional (String) -> String instance during Foo creation. Also, when you use this function, you don't have to specify the type of argument explicitly, the following will work: Foo { input -> "Hello $input!" } as well as Foo { "Hello $it!" }.
1

It would be easier in your case to have the interface in Java:

fun createFoo() : Foo = Foo { "hello $it" }

But as you have a Kotlin interface instead, you are a bit out of luck here. Here is a related issue regarding this: KT-7770

A workaround to this could be (but that mainly depends on how you use that interface) to have a Kotlin interface as follows in place that is the main entry point for the Java side:

interface Foo : (String) -> String

On the Kotlin side you will not use it and the Java side should only use it to deliver functions, e.g.

// Java class
public class JFooFactory implements FooFactory {
  @Override
  @NotNull
  public Foo createFoo() { // uses the Foo-interface from Kotlin
    return input -> "hello " + input;
  }
}

// Kotlin equivalent:
class KFactory : FooFactory {
  override fun createFoo() : (String) -> String = {
    "hello $it"
  }
}

where the corresponding FooFactory-interface could look like:

interface FooFactory {
  fun createFoo() : (String) -> String
}

Usage could look like:

listOf(KFooFactory(), JFooFactory())
     .map {
         it.createFoo()
     }
     .forEach { func : (String) -> String -> // i.e. func is of (sub)type (String) -> String
        func("demo") // calling it to deliver "hello demo" twice
     }

Alternatively, to have also that Foo-feeling for Kotlin you can do it as follows:

typealias Foo = (String) -> String
interface JFoo : Foo 
// or if you put the interface in its own package you could also use:   
interface Foo : someother.package.Foo

then the Java code stays the same as above, either with JFoo or with Foo pointing to that other package; the typealias is not visible from Java. The Kotlin side would change to the following:

class KFactory : FooFactory {
  override fun createFoo() : Foo = {
    "hello $it"
  }
}

The Factory-interface could also be replaced:

interface FooFactory {
  fun createFoo() : Foo
}

Under the hood however everything stays the same. We have/use (String) -> String in Kotlin and Foo-functional-interface in Java.

Comments

0

To use Kotlin lambda to Java SAM interface conversion anywhere you want, simply specify the name of the interface before the lambda.

fun createFoo(): Foo {
   return Foo { input:String -> "Hello $input!" }
}

It doesn't even need to be that long.

fun createFoo(): Foo {
   return Foo { "Hello $it!" }
}

As long as the interface has a single method and is declared in Java, that's all you need to do.

1 Comment

I updated my question, the interface is defined in kotlin not java.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.