面向对象基础:类与对象、属性与字段、构造函数、初始化块、成员函数

好,咱们今天聊聊面向对象。说实话,很多初学者觉得这玩意儿玄乎,其实说白了,它就是一套组织代码的「套路」。你想想看,现实世界里到处都是对象——你手里的手机、坐的椅子、敲代码的电脑。面向对象编程,就是把这种「万物皆对象」的思维搬到代码里。

我个人习惯把类理解成「图纸」,对象就是按图纸造出来的「实物」。一张图纸可以造无数个实物,一个类也可以实例化无数个对象。嗯,这个比喻虽然老套,但确实管用。

1. 类与对象:图纸与实物的关系

在 Kotlin 里定义一个类,用 class 关键字就行。简单到令人发指:

class Person {
    var name: String = ""
    var age: Int = 0
}

然后创建对象:

val person = Person()
person.name = "张三"
person.age = 25

你看,就这么简单。不过我在项目中遇到过一个问题——很多人把类写得跟数据容器似的,所有属性都公开,到处改来改去。后来维护起来那叫一个酸爽。所以我建议,一开始就想清楚哪些属性该暴露,哪些该藏起来。

2. 属性与字段:Kotlin 的贴心设计

Java 程序员转过来会觉得很爽。在 Java 里你要写 getter/setter,在 Kotlin 里?不存在的。Kotlin 的属性本身就是自带 getter 和 setter 的。

class User {
    var username: String = ""
        get() = field.uppercase()   // 读取时自动转大写
        set(value) {
            field = value.trim()    // 赋值时自动去空格
        }
}

这里有个关键概念叫 field。它是个「幕后字段」,你只能在 getter/setter 里访问它。为什么要这么设计?为了防止死循环。你想想看,如果在 setter 里直接写 this.username = value,那又会触发 setter,无限递归下去。用 field 就绕开了这个问题。

核心要点:

  • 属性 = 字段 + getter + setter
  • field 是幕后字段,只在 getter/setter 中可用
  • val 声明只读属性(只有 getter)
  • var 声明可变属性(有 getter 和 setter)

3. 构造函数:造对象的入口

Kotlin 的构造函数分两种:主构造函数和次构造函数。主构造函数直接写在类名后面:

class Student(val name: String, var age: Int)

就一行?对,就一行。Kotlin 就是这么简洁。主构造函数的参数可以直接声明为属性——用 valvar 修饰就行。

次构造函数用 constructor 关键字:

class Student(val name: String, var age: Int) {
    constructor(name: String) : this(name, 0)  // 委托给主构造函数
}

我曾经在项目里看到有人写了五六个次构造函数,看得我头皮发麻。其实大部分场景下,用默认参数就能搞定:

class Student(val name: String, var age: Int = 18)

这样调用的时候可以 Student("李四"),也可以 Student("李四", 20),灵活得很。

4. 初始化块:对象诞生时的「洗礼」

初始化块用 init 关键字,它在主构造函数之后、对象创建完成之前执行。说白了,就是给对象做「初始化仪式」的地方。

class DatabaseConfig(host: String, port: Int) {
    private val connectionString: String
    
    init {
        // 这里可以做校验、计算、日志等操作
        require(host.isNotBlank()) { "主机名不能为空" }
        require(port in 1..65535) { "端口号不合法" }
        connectionString = "jdbc:mysql://$host:$port"
        println("数据库配置初始化完成: $connectionString")
    }
}

注意,init 块可以有多个,按顺序执行。我个人习惯只写一个,除非逻辑确实需要拆分。写多了反而不好读。

小技巧:初始化块里适合做参数校验、资源初始化、日志记录。但别放耗时操作,比如网络请求——那应该放到专门的工厂方法或异步初始化里。

5. 成员函数:对象的行为

成员函数就是定义在类内部的函数,它描述了对象「能做什么」。

class Calculator {
    fun add(a: Int, b: Int): Int = a + b
    
    fun multiply(a: Int, b: Int): Int {
        return a * b
    }
}

调用的时候:

val calc = Calculator()
println(calc.add(3, 5))      // 8
println(calc.multiply(3, 5)) // 15

成员函数里可以访问类的属性和其他函数。这里有个细节——Kotlin 的函数默认是 final 的,不能被重写。如果你想允许子类重写,得加 open 关键字。这个咱们后面讲继承的时候再细说。

避坑指南:我曾经在项目里看到有人把成员函数写得巨长无比,一个函数几百行。记住,函数应该只做一件事。如果一个函数里既有计算、又有打印、还有网络请求,赶紧拆!

知识体系总览

下面这张图把本章的核心概念串起来了,你可以对照着理解:

面向对象基础 类与对象 图纸 vs 实物 class 关键字 实例化对象 属性与字段 getter/setter field 幕后字段 val vs var 构造函数 主构造函数 次构造函数 默认参数 初始化块 init 关键字 参数校验 资源初始化 成员函数 fun 关键字 对象行为 访问属性/其他函数 五大核心概念,共同构成 Kotlin 面向对象编程的基石

完整示例:综合运用

最后,咱们来个综合例子,把今天讲的东西串起来:

class BankAccount(
    val accountNumber: String,
    val owner: String,
    private var balance: Double = 0.0
) {
    init {
        require(accountNumber.isNotBlank()) { "账号不能为空" }
        require(owner.isNotBlank()) { "户主不能为空" }
        println("账户 $accountNumber 创建成功,户主:$owner")
    }
    
    // 只读属性,对外暴露余额
    val currentBalance: Double
        get() = balance
    
    // 存款
    fun deposit(amount: Double) {
        require(amount > 0) { "存款金额必须大于0" }
        balance += amount
        println("存入 $amount,当前余额:$balance")
    }
    
    // 取款
    fun withdraw(amount: Double): Boolean {
        if (amount <= 0 || amount > balance) {
            println("取款失败:金额不合法或余额不足")
            return false
        }
        balance -= amount
        println("取出 $amount,当前余额:$balance")
        return true
    }
}

// 使用
fun main() {
    val account = BankAccount("6222021234567890", "王五", 1000.0)
    account.deposit(500.0)
    account.withdraw(200.0)
    println("最终余额:${account.currentBalance}")
}

这个例子涵盖了类、主构造函数、初始化块、属性(包括私有属性和自定义 getter)、成员函数。你想想看,如果不用面向对象,这些逻辑散落在各个函数里,维护起来得多痛苦?

好了,今天的内容就到这里。记住,面向对象不是银弹,但它确实是组织代码的好工具。多用、多练,慢慢你就能体会到它的妙处了。


公众号:蓝海资料掘金营,微信deep3321