我们来详细探讨一下 Kotlin 中的抽象类。
在 Kotlin 中,抽象类是一种不能被直接实例化的类,其主要目的是作为其他类的基类(父类),用于定义通用的属性和方法,而将具体的实现细节留给子类。
1. 定义抽象类
在 Kotlin 中,使用 abstract 关键字来声明一个抽象类。
kotlin
kotlin
// 定义一个抽象类 Shape
abstract class Shape {
// 抽象属性(没有初始化值,必须在子类中重写)
abstract val name: String
// 抽象方法(没有方法体,必须在子类中重写)
abstract fun calculateArea(): Double
// 非抽象方法(有具体实现,可以被子类继承或重写)
fun printName() {
println("形状名称: $name")
}
}
关键点:
- 抽象类可以包含抽象属性 和抽象方法,这些必须在子类中实现。
- 抽象类也可以包含非抽象属性 和非抽象方法,这些会被子类继承。
- 抽象类不能被直接实例化 ,即不能使用
val shape = Shape()。
2. 继承抽象类
子类继承抽象类时,必须使用 override 关键字重写所有抽象属性和方法。
kotlin
kotlin
// 子类 Circle 继承自抽象类 Shape
class Circle(
val radius: Double
) : Shape() {
// 重写抽象属性
override val name: String = "圆形"
// 重写抽象方法
override fun calculateArea(): Double {
return Math.PI * radius * radius
}
}
// 子类 Rectangle 继承自抽象类 Shape
class Rectangle(
val width: Double,
val height: Double
) : Shape() {
override val name: String = "矩形"
override fun calculateArea(): Double {
return width * height
}
}
使用示例:
kotlin
scss
fun main() {
val circle = Circle(5.0)
circle.printName() // 输出:形状名称: 圆形
println("面积: ${circle.calculateArea()}") // 输出:面积: 78.5398...
val rectangle = Rectangle(4.0, 6.0)
rectangle.printName() // 输出:形状名称: 矩形
println("面积: ${rectangle.calculateArea()}") // 输出:面积: 24.0
}
3. 抽象类的特点
-
不能实例化:
kotlin
scssval shape = Shape() // 错误:抽象类不能被实例化 -
可以包含抽象和非抽象成员:
- 抽象成员(
abstract修饰)必须在子类中重写。 - 非抽象成员可以直接继承或被子类重写(使用
override)。
- 抽象成员(
-
子类必须实现所有抽象成员:如果子类没有实现所有抽象属性和方法,那么子类也必须声明为抽象类。
kotlin
kotlinabstract class Square( val sideLength: Double ) : Shape() { // 只重写了抽象属性,没有重写抽象方法 calculateArea() override val name: String = "正方形" // 因此 Square 类也必须是抽象的 } -
抽象类可以继承其他类:抽象类可以继承自另一个非抽象类或抽象类。
kotlin
kotlinopen class Animal { open fun makeSound() { println("动物发出声音") } } abstract class Dog : Animal() { override abstract fun makeSound() // 重写并声明为抽象方法,子类必须实现 } class Puppy : Dog() { override fun makeSound() { println("小狗汪汪叫") } }
4. 抽象类与接口的区别
在 Kotlin 中,抽象类和接口(interface)都可以用于定义抽象行为,但它们有几个关键区别:
| 特性 | 抽象类 (abstract class) |
接口 (interface) |
|---|---|---|
| 构造函数 | 可以有构造函数 | 不能有构造函数(Kotlin 1.9+ 支持接口中定义带默认实现的属性,但仍不能有构造函数) |
| 多重继承 | 只能单继承(一个类只能继承一个抽象类) | 可以实现多个接口 |
| 属性 | 可以包含非抽象属性 | 接口中的属性默认是抽象的,或必须提供 getter 实现 |
| 方法实现 | 可以包含非抽象方法的实现 | 接口中的方法默认是抽象的,或必须提供实现(Kotlin 1.4+ 支持接口方法的默认实现) |
| 访问修饰符 | 可以使用 private, protected, public |
接口成员默认是 public,不能有 private 修饰符 |
使用建议:
- 如果需要定义一个通用的基类,并且需要包含构造函数、非抽象属性或方法,使用抽象类。
- 如果只是需要定义一组抽象行为(方法或属性),并且希望类可以实现多个这样的行为,使用接口。
5. 总结
- 抽象类是用
abstract关键字声明的类,不能被直接实例化。 - 抽象类可以包含抽象成员(必须在子类中重写)和非抽象成员(可以直接继承)。
- 子类继承抽象类时,必须重写所有抽象成员,否则子类也必须是抽象类。
- 抽象类适合作为具有共同属性和行为的类的基类,而接口适合定义多个类可以实现的抽象行为。