Reputation: 5629
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
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
Reputation: 5629
SAM conversion will be supported with version 1.4, with a new type inference algorithm.
See:
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
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
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
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
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