Home > Back-end >  Use property getter method reference for functional (SAM) interface variables
Use property getter method reference for functional (SAM) interface variables

Time:11-08

I want to pass a property getter method reference as one of the function arguments, and have that argument be of my own functional interface type, but ran into an issue.

Here's a stripped down minimal reproducible case, I changed the variable from an argument into a property, but the issue is the same.

class Foo {
    val bar: Bar? = null
}

class Bar

fun interface FooBarSelector {
    fun select(foo: Foo): Bar?
}

class KotlinClass() {
    val selector: FooBarSelector = Foo::bar
}

This doesn't compile, Foo::bar is underlined and the error is

Type mismatch.
Required: FooBarSelector
Found: KProperty1<Foo, Bar?>

I tried to look this up, found similar questions about SAM conversions, but they were a bit different and I don't think any of them referred specifically to property getters.

I found that the issue can be solved by doing one of the following:

  1. Remove the explicit type, or replace it with the suggested KProperty1. Not an option, I want to preserve my type.
  2. Replace the method reference with FooBarSelector { it.bar }. Far from ideal, but better than the first option.

Why does this happen and are there any other options? I am new to Kotlin, but not Java.

Kotlin version used is 1.7.20

EDIT:

Here's my original goal: accept a FooBarSelector as an argument, and by default point it at a property getter:

fun doSomething(
    selector: FooBarSelector = Foo::bar //doesn't compile
) {
}

CodePudding user response:

Your code with the lambda is fine, but you may prefer this syntax for the same thing:

class KotlinClass() {
    val selector = FooBarSelector(Foo::bar)
}

Explanation:

Kotlin function references are more explicit about types than in Java, because function references are a first-class object type. When you want to use a function reference as a functional interface instance, you must convert it. This can be done automatically by the compiler using SAM conversion.

SAM conversion only works when passing a function reference as an argument to a function that has a parameter with a functional interface type. So, it doesn't directly work when assigning to a property.

But Kotlin implicitly provides higher order functions for functional interfaces that allow you to pass a function reference that will convert it into an interface instance. The implicit function is named after the interface, so it looks like a constructor call.

In the above code, the implicit functional interface "constructor" is inline, so there is no intermediate functional object allocated in the compiled code. This compiles to the same thing you would get in Java with a direct method reference.

CodePudding user response:

I am not 100% sure what you are expecting but consider this example:

class Foo(
  val bar: Bar
)

data class Bar(
  val value: String
)

interface FooBarSelector {
  fun select(foo: Foo): Bar {
    return foo.bar
  }
}

class FooBarCustomSelector: FooBarSelector {
  override fun select(foo: Foo): Bar {
    return Bar("I don't care about which Foo was passed. I'll return my own Bar")
  }
}

class KotlinClass(val selector: (Foo) -> Bar = Foo::bar)

fun main(args: Array<String>) {
  val kotlinClassWithDefaultSelector = KotlinClass()
  val kotlinClassWithCustomSelector = KotlinClass(FooBarCustomSelector()::select)
  val foo = Foo(Bar("Bar1"))
  println("kotlinClassWithDefaultSelector: ${kotlinClassWithDefaultSelector.selector(foo)}")
  println("kotlinClassWithCustomSelector: ${kotlinClassWithCustomSelector.selector(foo)}")
}

This would print:

kotlinClassWithDefaultSelector: Bar(value=Bar1)
kotlinClassWithCustomSelector: Bar(value=I don't care about which Foo was passed. I'll return my own Bar)
  • Related