在 Kotlin 中,继承(Inheritance) 和 委托(Delegation) 是两种核心的代码复用与扩展机制,二者设计目标不同:继承侧重「类的层级关系与功能继承」,委托侧重「复用已有实现、避免继承耦合」。下面结合语法、场景、区别详细说明:
一、继承(Inheritance)
继承的核心是「子类拥有父类的属性和方法」,遵循 单一继承原则 (一个类只能有一个直接父类),通过 : 语法实现。
1. 核心语法与规则
- 父类需用
open标记(默认final,不可继承); - 子类用
:继承父类,需传递父类构造参数; - 重写父类方法 / 属性需用
override关键字(父类成员需open)。
2. 示例:类的继承
kotlin
// 开放父类(允许继承)
open class Animal(val name: String) {
open fun eat() {
println("$name 在进食")
}
fun breathe() {
println("$name 在呼吸") // 非 open 方法,不可重写
}
}
// 子类继承父类
class Dog(name: String) : Animal(name) {
// 重写父类的 open 方法
override fun eat() {
println("$name 在吃骨头")
}
}
fun main() {
val dog = Dog("旺财")
dog.eat() // 输出:旺财 在吃骨头(重写后的方法)
dog.breathe() // 输出:旺财 在呼吸(继承的父类方法)
println(dog.name) // 输出:旺财(继承的父类属性)
}
3. 继承的核心特点
- is-a 关系 :子类是父类的一种(如
Dog是Animal的一种),语义强耦合; - 功能复用 :直接继承父类的非
final成员,子类可重写扩展; - 限制:单一继承(只能有一个直接父类),易造成类层级臃肿(如多层继承)。
二、委托(Delegation)
委托的核心是「将类的部分功能委托给另一个类实现」,通过 by 关键字语法糖实现,本质是「组合优于继承」设计原则的体现。
Kotlin 原生支持委托,无需手动编写转发代码(编译器自动生成),分为 类委托 和 属性委托 两类,核心是「类委托」(复用类功能)。
1. 类委托(核心场景)
语法:class 子类 : 接口 by 委托实例
- 子类实现某个接口,将接口的所有方法委托给一个「委托实例」;
- 子类可选择性重写接口方法(覆盖委托的实现)。
示例:类委托复用功能
需求:实现一个「日志集合」,复用 ArrayList 的存储功能,同时在添加 / 删除元素时打印日志。
kotlin
// 1. 定义接口(明确要委托的功能)
interface MyCollection<T> {
fun add(element: T): Boolean
fun remove(element: T): Boolean
fun size(): Int
}
// 2. 委托类(已有实现,需复用)
class MyArrayList<T> : MyCollection<T> {
private val list = ArrayList<T>()
override fun add(element: T): Boolean {
return list.add(element)
}
override fun remove(element: T): Boolean {
return list.remove(element)
}
override fun size(): Int {
return list.size
}
}
// 3. 目标类:通过委托复用 MyArrayList 的实现,添加日志功能
class LogCollection<T> : MyCollection<T> by MyArrayList<T>() {
// 选择性重写 add 方法(覆盖委托的实现,添加日志)
override fun add(element: T): Boolean {
println("添加元素:$element")
return super.add(element) // 调用委托类的 add 方法(MyArrayList 的实现)
}
// 选择性重写 remove 方法
override fun remove(element: T): Boolean {
println("删除元素:$element")
return super.remove(element)
}
}
fun main() {
val collection = LogCollection<String>()
collection.add("Kotlin") // 输出:添加元素:Kotlin
collection.add("Java") // 输出:添加元素:Java
println("集合大小:${collection.size()}") // 输出:集合大小:2(复用委托的 size 方法)
collection.remove("Java") // 输出:删除元素:Java
}
关键说明:
LogCollection实现MyCollection接口,但未手动实现add/remove/size方法,而是通过by MyArrayList<T>()将这些方法委托给MyArrayList实例;- 子类可重写接口方法,此时优先执行子类的实现(如
add方法的日志逻辑),再通过super.add(element)调用委托类的原实现; - 委托是「has-a 关系」(
LogCollection包含MyArrayList实例),语义耦合弱,灵活性高。
2. 属性委托(补充场景)
属性委托是「将属性的 getter/setter 逻辑委托给另一个类」,语法:val/var 属性名: 类型 by 委托实例,常用于复用属性逻辑(如延迟初始化、观察属性变化)。
示例:延迟初始化(lazy 是 Kotlin 内置的属性委托)
kotlin
// 延迟初始化:只有第一次访问时才执行初始化逻辑
val expensiveData: String by lazy {
println("初始化数据(耗时操作)")
"耗时计算后的结果"
}
fun main() {
println("开始访问数据")
println(expensiveData) // 输出:初始化数据(耗时操作)→ 耗时计算后的结果
println(expensiveData) // 输出:耗时计算后的结果(直接复用已初始化的值)
}
3. 委托的核心特点
- has-a 关系:目标类包含委托实例,语义耦合弱(可灵活替换委托对象);
- 多委托支持:一个类可实现多个接口,每个接口委托给不同实例(间接实现 "多继承" 效果,无单一继承限制);
- 解耦:避免继承带来的类层级臃肿,复用已有实现而不依赖父类层级;
- 灵活扩展:可选择性覆盖委托的方法,保留核心复用,扩展额外逻辑。
三、继承 vs 委托:核心区别与选择场景
| 维度 | 继承(Inheritance) | 委托(Delegation) |
|---|---|---|
| 语义关系 | is-a(子类是父类的一种) | has-a(目标类包含委托实例) |
| 耦合程度 | 强耦合(子类依赖父类实现) | 弱耦合(仅依赖接口,可替换委托) |
| 继承限制 | 单一继承(只能有一个直接父类) | 支持多委托(多个接口可委托给不同实例) |
| 类层级影响 | 易造成层级臃肿(多层继承) | 不影响类层级(扁平化设计) |
| 功能复用方式 | 继承父类所有非 final 成员 | 复用委托类的接口实现,可选择性覆盖 |
| 灵活性 | 低(父类变更可能影响子类) | 高(可动态替换委托实例,扩展逻辑) |
选择原则:
-
用 继承 的场景:
- 子类与父类存在明确的「is-a」关系(如
Dog是Animal); - 需要复用父类的大部分功能,且子类需融入父类的类层级(如多态场景)。
- 子类与父类存在明确的「is-a」关系(如
-
用 委托 的场景:
- 仅需复用某个类的部分功能(而非全部);
- 避免单一继承限制(需整合多个类的功能);
- 希望降低耦合,允许灵活替换实现(如切换不同的存储方案、日志方案);
- 类层级已复杂,不想再增加继承深度。
四、进阶:委托实现 "多继承" 效果
由于 Kotlin 禁止类的多继承,但委托支持多接口委托,可间接实现 "多继承" 的功能整合:
示例:一个类整合两个不同类的功能
kotlin
// 接口1:飞行功能
interface Flyable {
fun fly()
}
// 接口2:游泳功能
interface Swimmable {
fun swim()
}
// 委托类1:实现飞行功能
class Bird : Flyable {
override fun fly() {
println("鸟类飞行")
}
}
// 委托类2:实现游泳功能
class Fish : Swimmable {
override fun swim() {
println("鱼类游泳")
}
}
// 目标类:通过多委托整合飞行和游泳功能
class Duck : Flyable by Bird(), Swimmable by Fish() {
// 可选:覆盖委托的方法
override fun fly() {
println("鸭子低空飞行(覆盖鸟类飞行)")
}
}
fun main() {
val duck = Duck()
duck.fly() // 输出:鸭子低空飞行(覆盖鸟类飞行)
duck.swim() // 输出:鱼类游泳(复用鱼类的实现)
}
这里 Duck 同时整合了 Bird 的飞行功能和 Fish 的游泳功能,且无需继承这两个类,避免了多继承的问题。
五、核心总结
- 继承是「强耦合的层级关系」,适合
is-a场景,遵循单一继承; - 委托是「弱耦合的功能复用」,适合
has-a场景,支持多委托,灵活性更高; - Kotlin 推荐「组合(委托)优于继承」,减少类层级臃肿,提升代码可维护性;
- 需整合多个类的功能时,用「接口 + 多委托」替代多继承(避免歧义与耦合)。