Skip to content

函数式 (SAM

[//]: # (title: 函数式 (SAM) 接口)

只包含一个抽象成员函数的接口被称为 函数式接口,或 单抽象方法 (SAM) 接口。函数式接口可以有多个非抽象成员函数,但只能有一个抽象成员函数。

要在 Kotlin 中声明函数式接口,请使用 fun 修饰符。

kotlin
fun interface KRunnable {
   fun invoke()
}

SAM 转换

对于函数式接口,你可以使用 SAM 转换,通过 lambda 表达式使你的代码更简洁易读。

你可以使用 lambda 表达式,而无需手动创建实现函数式接口的类。通过 SAM 转换,Kotlin 可以将任何签名与接口的单个方法签名匹配的 lambda 表达式转换为动态实例化该接口实现的代码。

例如,考虑以下 Kotlin 函数式接口:

kotlin
fun interface IntPredicate {
   fun accept(i: Int): Boolean
}

如果你不使用 SAM 转换,你需要编写类似这样的代码:

kotlin
// 创建类的实例
val isEven = object : IntPredicate {
   override fun accept(i: Int): Boolean {
       return i % 2 == 0
   }
}

通过利用 Kotlin 的 SAM 转换,你可以编写以下等效代码:

kotlin
// 使用 lambda 创建实例
val isEven = IntPredicate { it % 2 == 0 }

一个简短的 lambda 表达式替代了所有不必要的代码。

kotlin
fun interface IntPredicate {
   fun accept(i: Int): Boolean
}

val isEven = IntPredicate { it % 2 == 0 }

fun main() {
   println("Is 7 even? - ${isEven.accept(7)}")
}

你也可以将 SAM 转换用于 Java 接口

从带构造函数的接口迁移到函数式接口

从 1.6.20 版本开始,Kotlin 支持对函数式接口构造函数使用可调用引用,这提供了一种源代码兼容的方式,可以从带构造函数的接口迁移到函数式接口。 考虑以下代码:

kotlin
interface Printer {
    fun print()
}

fun Printer(block: () -> Unit): Printer = object : Printer { override fun print() = block() }

在启用函数式接口构造函数的可调用引用后,此代码只需通过函数式接口声明即可替换:

kotlin
fun interface Printer {
    fun print()
}

其构造函数将被隐式创建,并且任何使用 ::Printer 函数引用的代码都将编译。例如:

kotlin
documentsStorage.addPrinter(::Printer)

通过使用 DeprecationLevel.HIDDEN 配合 @Deprecated 注解标记旧版 Printer 函数,可以保留二进制兼容性:

kotlin
@Deprecated(message = "关于此弃用的消息", level = DeprecationLevel.HIDDEN)
fun Printer(...) {...}

函数式接口与类型别名对比

你也可以简单地使用函数式类型的类型别名重写上述代码:

kotlin
typealias IntPredicate = (i: Int) -> Boolean

val isEven: IntPredicate = { it % 2 == 0 }

fun main() {
   println("Is 7 even? - ${isEven(7)}")
}

然而,函数式接口和类型别名服务于不同的目的。 类型别名只是现有类型的名称——它们不创建新类型,而函数式接口会。 你可以提供特定于某个函数式接口的扩展,使其不适用于普通函数或其类型别名。

类型别名只能有一个成员,而函数式接口可以有多个非抽象成员函数和一个抽象成员函数。 函数式接口还可以实现和扩展其他接口。

函数式接口比类型别名更灵活,并提供更多能力,但它们在语法和运行时都可能代价更高,因为它们可能需要转换为特定接口。 在代码中选择使用哪一个时,请考虑你的需求:

  • 如果你的 API 需要接受一个具有特定形参和返回类型的函数(任何函数),请使用简单的函数式类型或定义一个类型别名来为对应的函数式类型提供一个更短的名称。
  • 如果你的 API 接受的实体比函数更复杂——例如,它具有非平凡的契约和/或无法在函数式类型签名中表达的操作——请为其声明一个单独的函数式接口。