Chriss
Chriss

Reputation: 5629

How to implement a functional interface as lambda in Kotlin?

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

Upvotes: 7

Views: 5162

Answers (6)

antaki93
antaki93

Reputation: 860

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

Upvotes: 6

Chriss
Chriss

Reputation: 5629

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!

Upvotes: 3

EpicPandaForce
EpicPandaForce

Reputation: 81539

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.

Upvotes: 2

Roland
Roland

Reputation: 23242

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.

Upvotes: 1

Leo Aso
Leo Aso

Reputation: 12463

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.

Upvotes: 0

Michael Stummvoll
Michael Stummvoll

Reputation: 11

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!"}
}

Upvotes: 1

Related Questions