函数式 (SAM) 接口
最终更新: 2024/03/21
只有一个抽象方法的接口称为 函数式接口 (Functional Interface), 或者叫做 单抽象方法(SAM, Single Abstract Method) 接口. 函数式接口可以拥有多个非抽象的成员, 但只能拥有一个抽象成员.
在 Kotlin 中声明函数式接口时, 请使用 fun
修饰符.
fun interface KRunnable {
fun invoke()
}
SAM 转换功能
对于函数式接口, 可以通过 SAM 转换功能, 使用 Lambda 表达式, 让你的代码更加简洁易读.
你可以使用 Lambda 表达式, 而不必手动的创建一个类, 实现函数式接口. 只要 Lambda 表达式的签名与接口的唯一方法的签名相匹配, Kotlin 可以通过 SAM 转换功能, 将任意的 Lambda 表达式转换为一段代码, 创建一个实现接口的类的实例.
比如, 对于下面的 Kotlin 函数式接口:
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
如果不使用 SAM 转换功能, 那么就需要编写这样的代码:
// 创建类的实例
val isEven = object : IntPredicate {
override fun accept(i: Int): Boolean {
return i % 2 == 0
}
}
使用 Kotlin 的 SAM 转换功能, 就可以编写下面的代码, 效果相同:
// 使用 Lambda 表达式创建实例
val isEven = IntPredicate { it % 2 == 0 }
这样, 就通过更加简短的 Lambda 表达式代替了所有其他不必要的代码.
fun interface IntPredicate {
fun accept(i: Int): Boolean
}
val isEven = IntPredicate { it % 2 == 0 }
fun main() {
println("Is 7 even? - ${isEven.accept(7)}")
}
也可以使用 对 Java 接口的 SAM 转换功能.
从带构造器函数的接口 迁移到函数式接口
从 1.6.20 开始, Kotlin 支持对函数式接口构造器的 可调用的引用, 因此增加了一种源代码兼容的方式, 可以从带构造器函数的接口迁移到函数式接口. 我们来看看以下代码:
interface Printer {
fun print()
}
fun Printer(block: () -> Unit): Printer = object : Printer { override fun print() = block() }
由于可以使用对函数式接口构造器的可调用的引用, 这段代码可以替换为函数式接口声明:
fun interface Printer {
fun print()
}
它的构造器会隐含的创建, 使用 ::Printer
函数引用的任何代码都可以正确编译. 比如:
documentsStorage.addPrinter(::Printer)
如果要保留二进制兼容性, 可以对过去的函数 Printer
标记
@Deprecated
注解, 注解参数是 DeprecationLevel.HIDDEN
:
@Deprecated(message = "Your message about the deprecation", level = DeprecationLevel.HIDDEN)
fun Printer(...) {...}
函数式接口 与 类型别名(Type Alias)
你也可以对函数类型使用 类型别名(Type Alias), 简单的重写上面的代码:
typealias IntPredicate = (i: Int) -> Boolean
val isEven: IntPredicate = { it % 2 == 0 }
fun main() {
println("Is 7 even? - ${isEven(7)}")
}
但是, 函数式接口 与 类型别名(Type Alias) 服务于不同的目的. 类型别名只是对已有的类型提供一个新的名称 – 它不会创建新的类型, 而函数式接口会. 对某个特定的函数式接口, 你可以提供扩展, 但对通常的函数或函数的类型别名则不可以.
类型别名只能拥有一个成员, 而函数式接口可以拥有多个非抽象的成员和一个抽象成员. 函数式接口也可以实现或继承其他接口.
函数式接口比类型别名更加灵活, 也提供了更多功能, 但语法上以及在运行时刻都存在更多代价, 因为需要转换为特定的接口. 当在你的代码中需要选择使用哪一种时, 应该考虑你的需求:
- 如果你的 API 需要接受一个函数 (任意的函数), 带有某些特定参数和返回类型 – 可以使用简单的函数类型, 或者为这个函数类型定义一个类型别名, 使得它的名称更简短.
- 如果你的 API 需要接受比函数更加复杂的实体 – 比如, 它带有比较重要的规约 和/或 操作, 无法表达为函数类型的签名 – 那么需要为它定义一个单独的函数式接口.