Kotlin 接口
接口(Interface)是 OOP 的另一个核心抽象机制——定义"能做什么"的能力契约。和继承(定义"是什么")不同,接口强调能力而非身份。Kotlin 的接口和 Java 8+ 类似,但语法更简洁、功能更纯粹。
1. 接口基础
用 interface 关键字声明,类用 : 实现接口(和继承语法一致):
// 接口定义:interface 关键字
interface Clickable {
fun click() // 抽象方法(无 body)
fun info() = "可点击的" // 默认实现(有 body)
}
// 实现接口
class Button : Clickable {
override fun click() { // 必须实现抽象方法
println("按钮被点击")
}
// info() 用默认实现,无需 override
}
val b = Button()
b.click() // 按钮被点击
println(b.info()) // 可点击的接口可以有抽象方法(无 body,实现类必须 override)和默认实现方法(有 body,实现类可选择性 override)。这一开始可能让 Java 7 用户惊讶——Java 8 才有默认方法,Kotlin 一开始就有。
2. 接口的默认实现
默认实现让接口可以渐进演化——加新方法时,旧的实现类不需要改动:
// 接口方法可以有默认实现
interface Greeter {
// 默认实现的方法
fun greet(name: String): String {
return "你好, $name"
}
// 抽象方法(无 body)
fun farewell(): String
// 也可以有抽象属性
val target: String
}
class EnglishGreeter(override val target: String) : Greeter {
// greet 用默认实现
override fun farewell() = "Goodbye"
}
class ChineseGreeter(override val target: String) : Greeter {
override fun greet(name: String) = "你好 $name,目标 $target"
override fun farewell() = "再见"
}
println(EnglishGreeter("class").greet("小明")) // 你好, 小明
println(ChineseGreeter("class").greet("小明")) // 你好 小明,目标 class接口默认实现的本质:实现类继承了一份默认代码,可以选择用或重写。这让接口比抽象类更灵活(因为单继承,抽象类不能"叠加")。
3. 多实现 + 冲突解决
这是接口相对抽象类的最大优势——一个类可以实现多个接口。如果两个接口有同名默认方法,Kotlin 强制你 override 解决:
// 类可以实现多个接口(关键差异!)
interface A {
fun foo() { println("A.foo") }
fun bar()
}
interface B {
fun foo() { println("B.foo") }
fun baz()
}
// C 同时实现 A 和 B(多实现)
class C : A, B {
override fun bar() { println("C.bar") }
override fun baz() { println("C.baz") }
// foo() 在两个接口都有默认实现,【必须 override 解决冲突】
override fun foo() {
// 用 super<T>.方法() 调用指定接口的版本
super<A>.foo()
super<B>.foo()
}
}
val c = C()
c.foo()
// A.foo
// B.foo
c.bar()
c.baz()关键语法:用 super<接口名>.方法() 调用指定接口的版本。Java 也是这套(Interface.super.method())。Kotlin 编译器会强制你处理冲突,避免歧义。
4. 接口属性
接口可以声明抽象属性,但不能有幕后字段(backing field)——因为接口是无状态的:
// 接口里的属性:【抽象的,没有幕后字段】
interface Vehicle {
val wheelCount: Int // 抽象属性,实现类必须提供
fun describe(): String
}
class Car : Vehicle {
override val wheelCount = 4 // 实现
override fun describe() = "汽车,$wheelCount 个轮子"
}
class Bicycle : Vehicle {
override val wheelCount = 2
override fun describe() = "自行车,$wheelCount 个轮子"
}
// 接口属性也可以有 getter(但访问幕后字段是不允许的)
interface Named {
val name: String
val upperName: String
get() = name.uppercase() // OK,调用抽象属性
}
class User(override val name: String) : Named
val u = User("小明")
println(u.upperName) // 小明实现类必须 override 提供属性值。接口里也可以有带 getter 的属性(但没有幕后字段,getter 必须基于抽象属性或方法)。
5. 接口 vs 抽象类(关键!)
这是 OOP 设计的经典话题。Kotlin 的差异表:
// 接口 vs 抽象类:核心区别
//
// 【接口 interface】
// - 一个类可以实现多个接口(多实现)
// - 不能有状态(幕后字段),但可以有抽象属性
// - 默认方法是 public 的(Java 9+ 才有 private)
// - 用于"can-do"能力描述(Comparable、Iterable 等)
// - 表达:这个类"能做什么"
//
// 【抽象类 abstract class】
// - 一个类只能继承一个抽象类(单继承)
// - 可以有状态(属性、幕后字段)
// - 可以有构造函数
// - 用于"is-a"关系描述(Animal -> Dog)
// - 表达:这个类"是什么"
//
// Kotlin 和 Java 8+ 的接口非常类似
// 主要差异:Kotlin 接口属性没有幕后字段,Java 接口属性是 static final实战建议:
- 优先用接口:多实现 + 灵活 + 不破坏单继承。
- 抽象类用于:有共享状态(字段)需要子类继承,或构造函数逻辑需要复用。
- 记住:接口描述能力(can-do),抽象类描述身份(is-a)。
6. 函数式接口(Fun Interface,SAM 转换)
用 fun interface 声明的单抽象方法接口(SAM,Single Abstract Method),可以用 lambda 实例化——类似 Java 的函数式接口:
// 函数式接口(Functional Interface,只有一个抽象方法)
// 用 fun interface 声明
fun interface Printer {
fun print(msg: String)
}
// 可以用 lambda 实例化(像 Java 的 SAM 转换)
val p1 = Printer { msg -> println("打印: $msg") }
val p2 = Printer { println(it) } // it 是隐式参数
p1.print("hello") // 打印: hello
p2.print("world") // world
// 对比传统写法
val p3 = object : Printer {
override fun print(msg: String) {
println("传统: $msg")
}
}
// 函数式接口常用于:回调、事件监听、策略模式函数式接口和函数类型 (T) -> R 的区别:函数式接口有名字(可读性更好),实现类可以加默认方法,可以多次用 lambda 实例化。适合"有名字的回调"。
7. 标准库常用接口
Kotlin 标准库大量用接口定义抽象,几个核心的:
- Iterable<T> / Collection<T> / List<T>:集合层次。
- Comparable<T>:定义自然顺序,实现
compareTo。 - Iterator<T>:迭代器,实现
next()和hasNext()。 - Closeable / AutoCloseable:可关闭资源,配合
use { }自动关闭。 - Runnable:可运行(从 Java 继承)。
8. 实战:Comparable + Iterable
// 实战:Comparable 接口(标准库内置)
data class Student(val name: String, val score: Int) : Comparable<Student> {
// 实现 compareTo:定义自然顺序
override fun compareTo(other: Student): Int {
// 按 score 降序,score 相同按 name 升序
return compareValuesBy(this, other, { -it.score }, { it.name })
}
}
val students = listOf(
Student("小明", 92),
Student("小红", 88),
Student("小刚", 95),
Student("小张", 88)
)
val sorted = students.sorted() // 调用 Comparable
println(sorted)
// [Student(name=小刚, score=95), Student(name=小明, score=92),
// Student(name=小红, score=88), Student(name=小张, score=88)]
// Iterable 接口:让自定义类支持 for-each
class NumberRange(val start: Int, val end: Int) : Iterable<Int> {
override fun iterator(): Iterator<Int> = (start..end).iterator()
}
for (n in NumberRange(1, 5)) {
print("$n ")
}
// 1 2 3 4 5实现 Comparable 后,你的类就可以用 sorted()、maxOrNull()、binarySearch 等标准库函数。实现 Iterable 后,你的类可以用 for (x in obj)、map、filter——这是 Kotlin 集合 API 的强大根源。
9. 接口的委托(by 关键字)
Kotlin 支持接口委托——把接口的实现转交给另一个对象。这是组合模式(优于继承)的语言级支持:
interface Logger {
fun log(msg: String)
}
class ConsoleLogger : Logger {
override fun log(msg: String) = println("[LOG] $msg")
}
// by 把 Logger 接口的实现委托给 logger 参数
class Service(logger: Logger) : Logger by logger {
fun doWork() {
log("开始工作") // 自动转发到 logger
// ...
log("完成")
}
}
val s = Service(ConsoleLogger())
s.doWork()
// [LOG] 开始工作
// [LOG] 完成
// 也可以选择性 override 某些方法
class TimedLogger(private val inner: Logger) : Logger by inner {
override fun log(msg: String) {
inner.log("[${System.currentTimeMillis()}] $msg")
}
}接口委托是 Kotlin 组合优于继承原则的语言级支持。比 Java 的"显式写转发方法"省心得多。
10. 接口设计的几个原则
- 接口 segregation(隔离):多个专门的小接口胜过一个大而全的接口。比如 List、Set 都继承 Collection,而不是把所有方法堆在 Collection。
- 避免"标签接口":无方法的接口(如 Java 的 Serializable)价值有限,优先用标记注解或类型参数。
- 默认方法慎用:默认方法让接口演化更灵活,但用多了会让控制流难以追踪。
- 接口不要有状态:接口的属性是抽象的,不能保存状态,这是接口的本质。
小结
这一章你掌握了 Kotlin 接口的所有姿势:interface 声明、默认实现、多实现、冲突解决、接口属性、函数式接口(SAM)、标准库常用接口、接口委托(by)。最大的差异是接口可以有默认实现(Java 8 才有),且支持多实现。下一篇看 Kotlin 的Lambda 与高阶函数——函数式编程。
← 上一篇 Kotlin 继承
下一篇 Kotlin Lambda 与高阶函数 →