kotlin抽象类(与接口的区别)

96 阅读3分钟

我们来详细探讨一下 Kotlin 中的抽象类。

在 Kotlin 中,抽象类是一种不能被直接实例化的类,其主要目的是作为其他类的基类(父类),用于定义通用的属性和方法,而将具体的实现细节留给子类。

1. 定义抽象类

在 Kotlin 中,使用 abstract 关键字来声明一个抽象类。

kotlin

// 定义一个抽象类 Shape
abstract class Shape {
    // 抽象属性(没有初始化值,必须在子类中重写)
    abstract val name: String

    // 抽象方法(没有方法体,必须在子类中重写)
    abstract fun calculateArea(): Double

    // 非抽象方法(有具体实现,可以被子类继承或重写)
    fun printName() {
        println("形状名称: $name")
    }
}

关键点

  • 抽象类可以包含抽象属性抽象方法,这些必须在子类中实现。
  • 抽象类也可以包含非抽象属性非抽象方法,这些会被子类继承。
  • 抽象类不能被直接实例化,即不能使用 val shape = Shape()

2. 继承抽象类

子类继承抽象类时,必须使用 override 关键字重写所有抽象属性和方法。

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

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. 抽象类的特点

  1. 不能实例化

    kotlin

    val shape = Shape() // 错误:抽象类不能被实例化
    
  2. 可以包含抽象和非抽象成员

    • 抽象成员(abstract 修饰)必须在子类中重写。
    • 非抽象成员可以直接继承或被子类重写(使用 override)。
  3. 子类必须实现所有抽象成员:如果子类没有实现所有抽象属性和方法,那么子类也必须声明为抽象类。

    kotlin

    abstract class Square(
        val sideLength: Double
    ) : Shape() {
        // 只重写了抽象属性,没有重写抽象方法 calculateArea()
        override val name: String = "正方形"
        
        // 因此 Square 类也必须是抽象的
    }
    
  4. 抽象类可以继承其他类:抽象类可以继承自另一个非抽象类或抽象类。

    kotlin

    open 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+ 支持接口方法的默认实现)
访问修饰符可以使用 privateprotectedpublic接口成员默认是 public,不能有 private 修饰符

使用建议

  • 如果需要定义一个通用的基类,并且需要包含构造函数、非抽象属性或方法,使用抽象类
  • 如果只是需要定义一组抽象行为(方法或属性),并且希望类可以实现多个这样的行为,使用接口

5. 总结

  • 抽象类是用 abstract 关键字声明的类,不能被直接实例化。
  • 抽象类可以包含抽象成员(必须在子类中重写)和非抽象成员(可以直接继承)。
  • 子类继承抽象类时,必须重写所有抽象成员,否则子类也必须是抽象类。
  • 抽象类适合作为具有共同属性和行为的类的基类,而接口适合定义多个类可以实现的抽象行为。