6

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

user2340612
  • 10,053
  • 4
  • 41
  • 66
Chriss
  • 5,157
  • 7
  • 41
  • 75
  • 1
    SAM conversion for Kotlin interfaces is not allowed [_by design_](https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions). 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." – user2340612 Nov 05 '19 at 15:50
  • 1
    Here is a related issue regarding this: [KT-7770](https://youtrack.jetbrains.com/issue/KT-7770) – Roland Nov 05 '19 at 15:53

6 Answers6

4

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

antaki93
  • 704
  • 7
  • 10
  • Just in case. It is not necessary to use functional interface at all. In the Kotlin world it is shorter to just use `(String) -> String` instead of `Foo` as the return type of your function. – antaki93 Dec 23 '22 at 14:11
3

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!

Chriss
  • 5,157
  • 7
  • 41
  • 75
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.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
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!"}
}
  • I updated my question, the interface is defined in kotlin not java. – Chriss Nov 05 '19 at 15:23
  • @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!" }`. – IlyaMuravjov Nov 05 '19 at 16:15
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.

Roland
  • 22,259
  • 4
  • 57
  • 84
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.

Leo Aso
  • 11,898
  • 3
  • 25
  • 46