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),清晰表达其作为基类的身份。
相关推荐
火柴就是我1 小时前
NekoBoxForAndroid 编译libcore.aar
android
组合缺一1 小时前
Spring Boot 国产化替代方案。Solon v3.7.2, v3.6.5, v3.5.9 发布(支持 LTS)
java·后端·spring·ai·web·solon·mcp
s***11701 小时前
常见的 Spring 项目目录结构
java·后端·spring
7***47711 小时前
在2023idea中如何创建SpringBoot
java·spring boot·后端
Kaede62 小时前
MySQL中如何使用命令行修改root密码
android·mysql·adb
IT_陈寒2 小时前
React性能优化:这5个Hooks技巧让我减少了40%的重新渲染
前端·人工智能·后端
L***d6703 小时前
十七:Spring Boot依赖 (2)-- spring-boot-starter-web 依赖详解
前端·spring boot·后端
本妖精不是妖精3 小时前
基于 Rokid Max 与 JSAR 构建空间锚定型 AR 信息面板
后端·ar·restful