14.Kotlin 类:类的形态(一):抽象类 (Abstract Class)

希望帮你在Kotlin进阶路上少走弯路,在技术上稳步提升。当然,由于个人知识储备有限,笔记中难免存在疏漏或表述不当的地方,也非常欢迎大家提出宝贵意见,一起交流进步。 ------ Android_小雨

整体目录:Kotlin 进阶不迷路:41 个核心知识点,构建完整知识体系

一、前言

在面向对象编程(OOP)的世界里,我们经常会遇到一种情况:我们能够清晰地描述一类事物的共同特征,但对于某些具体的行为,却无法在父类中给出确切的定义。

1.1 抽象类的核心价值

抽象类(Abstract Class)的核心价值在于定义共性模板约束子类实现。 它像是一个半成品的蓝图,规定了建筑的基本结构(共性),但留下了某些具体的装修风格(个性)给子类去完成。

1.2 Kotlin 抽象类的设计初衷

Kotlin 设计抽象类的初衷,是在代码复用子类规范之间寻找平衡。

  • 复用:将通用的状态和逻辑提取到父类,避免重复代码。
  • 规范:通过抽象方法强制子类必须实现特定逻辑,保证业务流程的完整性。

1.3 本文核心内容预告

本文将带您深入 Kotlin 抽象类的世界,路线如下:

  • 基础定义:语法与混合成员结构。
  • 核心特性:不可实例化、构造函数支持与强制约束。
  • 继承实战:4 种实现抽象成员的高级技巧。
  • 实战场景:BaseActivity、业务模板与共性逻辑封装。

二、抽象类基础:定义与语法规范

2.1 基本语法

在 Kotlin 中,使用 abstract 关键字来修饰一个类,使其成为抽象类。

kotlin 复制代码
abstract class BaseTemplate {
    // ...
}

2.2 抽象成员

使用 abstract 修饰的属性或方法,称为抽象成员。它们没有具体的实现(没有方法体,也没有属性初始化器),仅定义了"签名"。

2.3 非抽象成员

抽象类中可以包含普通(非抽象)的成员。这些成员可以有具体的实现,供子类直接复用。这是抽象类区别于接口的重要特征之一。

2.4 基础示例(定义抽象基类)

让我们看一个包含所有成员类型的完整示例:

kotlin 复制代码
abstract class Animal(
    open val name: String // 构造函数中的属性
) {
    // 2.3 非抽象成员:有状态,有实现
    val creationTime = System.currentTimeMillis()

    fun sleep() {
        println("$name 正在睡觉")
    }

    // 2.2 抽象成员:无状态,无实现,强制子类定义
    abstract val species: String
    abstract fun makeSound()

    // open 成员:有实现,但允许子类重写
    open fun move() {
        println("移动中...")
    }
}

三、抽象类的核心特性

根据 Kotlin 官方文档,抽象类有以下核心法则:

3.1 不可直接实例化

抽象类是"半成品",因此你不能直接创建抽象类的实例。

kotlin 复制代码
fun main() {
    // ❌ 编译错误:Cannot create an instance of an abstract class
    // val animal = Animal("Test")
}

3.2 子类的强制约束

如果一个非抽象类继承了抽象类,它必须实现(Override)父类中所有的抽象成员。这是抽象类的核心契约精神。

3.3 支持构造函数

与接口不同,抽象类可以拥有构造函数(主构造函数或次构造函数)和 init 代码块,用于初始化抽象类中的状态(State)。

3.4 兼容继承规则

  • 抽象成员 :隐含 open,必须被重写。
  • 非抽象成员 :默认为 final(不可重写)。如果希望子类能重写非抽象成员,必须显式加上 open 关键字。

四、抽象类的继承与实现步骤

4.1 子类继承抽象类

使用冒号 : 进行继承,并必须调用父类的构造函数。

4.2 实现抽象属性 / 方法

这是进阶的关键点。在 Kotlin 中,实现抽象成员有 4 种官方认可的合法方式

kotlin 复制代码
abstract class Base {
    abstract val size: Int
}

class Concrete : Base() {
    // 方式 1:直接赋值(最常见,生成 backing field 占用内存)
    override val size: Int = 100

    // 方式 2:自定义 Getter(推荐用于计算属性,不占内存)
    // override val size: Int get() = 100

    // 方式 3:逻辑计算
    // override val size: Int get() = if (System.currentTimeMillis() > 0) 100 else 0

    // 方式 4:var + setter(少见但合法)
    // override var size: Int = 100
}

4.3 重写非抽象成员

子类可以重写父类中标记为 open 的非抽象成员,并通过 super 关键字调用父类逻辑。

4.4 完整实战示例

我们将 Shape 抽象类落地为具体的 Circle

kotlin 复制代码
// 抽象父类
abstract class Shape(val color: String) {
    abstract fun calculateArea(): Double

    open fun printInfo() {
        println("I am a $color shape.")
    }
}

// 子类实现
class Circle(color: String, val radius: Double) : Shape(color) {
    // 必须实现抽象方法
    override fun calculateArea(): Double = Math.PI * radius * radius

    // 选择性重写 open 方法
    override fun printInfo() {
        super.printInfo() // 复用父类逻辑
        println("Type: Circle, Radius: $radius")
    }
}

五、抽象类的典型应用场景

5.1 框架基础模板 (Android BaseActivity)

利用抽象类统一生命周期管理和初始化流程,这是 Android 开发中最经典的应用。

kotlin 复制代码
abstract class BaseActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(getLayoutId()) // 模板逻辑:设置布局
        initViews()                   // 模板逻辑:初始化视图
        initData()                    // 模板逻辑:加载数据
    }

    // 强制子类提供布局 ID
    abstract fun getLayoutId(): Int

    // 强制子类实现初始化逻辑
    abstract fun initViews()
    abstract fun initData()
}

5.2 业务规范定义 (支付策略)

强制子类实现核心业务逻辑,确保业务完整性。

kotlin 复制代码
abstract class PaymentProcessor {
    // 核心支付逻辑,强制子类实现(支付宝、微信、银行卡逻辑不同)
    abstract fun pay(amount: Double)

    // 公共逻辑:支付前检查(可复用,也可重写)
    open fun checkRisk(): Boolean {
        println("执行通用风控检查...")
        return true
    }
}

5.3 共性逻辑封装 (Template Method 模式)

在 ViewModel 或 Presenter 层,封装数据加载的"骨架"。

kotlin 复制代码
abstract class BaseViewModel {
    // 模板方法:定义加载数据的标准流程
    fun loadData() {
        showLoading()
        try {
            val data = fetchData() // 具体获取数据的逻辑由子类决定
            showSuccess(data)
        } catch (e: Exception) {
            showError(e)
        }
    }

    abstract fun fetchData(): Any
    // ... 省略 showLoading 等通用实现
}

六、抽象类的使用边界与注意事项

6.1 与普通类的核心区别

特性 普通类 (Class) 抽象类 (Abstract Class)
实例化 可以直接创建对象 不可实例化
成员修饰 默认为 final 默认为 open (抽象成员),支持 abstract 修饰
用途 具体业务实现 继承体系的基石/模板

6.2 与接口的初步区分

这是架构选型时的关键决策点:

特性 抽象类 接口 (Interface)
状态 (State) 支持(有 backing field) 不支持(无状态)
构造函数 支持 不支持
多继承 不支持(单继承) 支持
设计意图 Is-A (它是...)、模板复用 Can-Do (它能...)、行为契约

6.3 避坑点 (常见编译错误)

错误信息 原因与解法
Abstract member not implemented 子类忘记实现抽象方法。解法:添加 override 实现。
Property must be initialized or be abstract 属性没赋值也没加 abstract。解法:初始化或声明为 abstract。
This type is final 试图继承普通类。解法:父类加 open 或 abstract。

七、总结与最佳实践

7.1 核心知识点回顾

  • 关键字abstract
  • 三大特征:不可实例化、包含抽象成员、强制子类实现。
  • 继承规则:单继承,抽象成员必须 override,非抽象成员需 open 才可 override。

7.2 适用场景

当多个类之间存在明显的家族相似性 (Is-A 关系),且你需要复用代码 (状态/具体方法)同时又需要强制约束某些行为时,抽象类是最佳选择。

7.3 实践建议

  • 多组合,少继承:抽象类虽好,但继承耦合度高。仅在确实需要模板复用时使用。
  • 命名规范 :通常以 Base 开头(如 BaseActivity)或直接使用名词(如 Shape),清晰表达其作为基类的身份。
相关推荐
俩个逗号。。2 分钟前
Kotlin 扩展函数详解
开发语言·kotlin
波波0073 小时前
ASP.NET MVC 中的返回类型全集详解
后端·asp.net·mvc
糟糕好吃8 小时前
我让 AI 操作网页之后,开始不想点按钮了
前端·javascript·后端
段娇娇9 小时前
Android jetpack LiveData(一)使用篇
android·android jetpack
XiaoLeisj9 小时前
Android Jetpack 页面架构实战:从 LiveData、ViewModel 到 DataBinding 的生命周期管理与数据绑定
android·java·架构·android jetpack·livedata·viewmodel·databinding
leonkay9 小时前
Golang语言闭包完全指南
开发语言·数据结构·后端·算法·架构·golang
颜酱10 小时前
BFS 与并查集实战总结:从基础框架到刷题落地
javascript·后端·算法
无限大610 小时前
数字生存02:如何在信息爆炸的时代保持清醒,不被算法控制
后端
无限大610 小时前
AI实战02:一个万能提示词模板,搞定90%的文案/设计/分析需求
前端·后端
青柠代码录11 小时前
【Linux】脚本:console.log 日志定期备份清理
后端