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

实战建议:

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 标准库大量用接口定义抽象,几个核心的:

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)mapfilter——这是 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. 接口设计的几个原则

小结

这一章你掌握了 Kotlin 接口的所有姿势:interface 声明、默认实现、多实现、冲突解决、接口属性、函数式接口(SAM)、标准库常用接口、接口委托(by)。最大的差异是接口可以有默认实现(Java 8 才有),且支持多实现。下一篇看 Kotlin 的Lambda 与高阶函数——函数式编程。

← 上一篇 Kotlin 继承

下一篇 Kotlin Lambda 与高阶函数

✈️💬