继承与接口¶
Kotlin 支持面向对象编程,但它对继承更保守:类和成员默认不可继承、不可覆盖。这个默认值与 Java 相反,背后的设计目标是让扩展点显式化。
Any:所有类的共同父类¶
没有显式父类时,Kotlin 类继承自 Any:
class User
Any 提供:
equals()hashCode()toString()
它类似 Java 的 Object,但方法集合更小。例如 Java 的 wait()、notify() 属于 JVM 对象模型,Kotlin 的 Any 不把它们作为跨平台公共 API 暴露。
类默认 final¶
class Base
// class Child : Base() // 编译错误
允许继承需要显式 open:
open class Base
class Child : Base()
Java 默认允许继承,除非写 final。Kotlin 默认禁止继承,除非写 open。这能降低无意间被继承导致的维护风险。
方法覆盖¶
open class Shape {
open fun draw() {
println("shape")
}
fun fill() {
println("fill")
}
}
class Circle : Shape() {
override fun draw() {
println("circle")
}
}
要覆盖父类成员,父类成员必须是 open,子类成员必须写 override。如果没有 override,编译器会报错。
override 成员默认也是开放的。如果不希望继续被子类覆盖:
open class Rectangle : Shape() {
final override fun draw() {
println("rectangle")
}
}
属性覆盖¶
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount: Int = 4
}
val 可以被 var 覆盖,因为 var 提供 getter 和 setter;反过来不行,因为父类如果承诺可写,子类不能把可写能力拿掉。
抽象类¶
abstract class Repository<T> {
abstract fun findById(id: Long): T?
fun requireById(id: Long): T =
findById(id) ?: error("not found: $id")
}
抽象类天然用于继承,不需要额外写 open。抽象成员没有实现,子类必须实现。
接口¶
interface Clickable {
fun click()
fun doubleClick() {
click()
click()
}
}
Kotlin 接口可以有抽象方法,也可以有默认实现。接口不能直接存储状态,但可以声明属性:
interface Named {
val name: String
}
class User(override val name: String) : Named
接口属性要求实现类提供这个属性,不代表接口里有字段。
接口继承¶
interface Named {
val name: String
}
interface Person : Named {
val firstName: String
val lastName: String
override val name: String
get() = "$firstName $lastName"
}
实现 Person 的类只需要提供缺失的成员:
data class Employee(
override val firstName: String,
override val lastName: String,
val role: String
) : Person
多接口冲突¶
interface A {
fun foo() = println("A")
}
interface B {
fun foo() = println("B")
}
class C : A, B {
override fun foo() {
super<A>.foo()
super<B>.foo()
}
}
如果多个接口提供同名默认实现,类必须明确选择如何处理冲突。
继承还是组合¶
Kotlin 语法鼓励你少用继承。继承适合:
- 真实的 is-a 关系。
- 框架要求的扩展点。
- 抽象模板方法。
- 封闭层级配合密封类。
组合适合:
- 复用行为。
- 替换策略。
- 避免基类状态和初始化顺序复杂化。
如果只是想复用几个工具方法,优先考虑扩展函数、组合对象或委托,而不是创建父类。
实践建议¶
- 不要为了“以后可能扩展”随手写
open。 - 覆盖成员时认真考虑是否还允许子类继续覆盖。
- 接口适合表达能力,抽象类适合共享状态和模板流程。
- 多接口默认实现冲突时,不要只机械调用两个
super,要明确业务语义。 - Java 框架需要代理类时,可能需要 Kotlin all-open 插件或框架专用插件。
参考¶
- 官方继承文档:https://kotlinlang.org/docs/inheritance.html
- 官方接口文档:https://kotlinlang.org/docs/interfaces.html