【Kotlin系列04】类与对象基础:从Java Bean到Data Class的优雅蜕变

引言:那个让我重写的100行Java Bean

还记得刚接触Kotlin时,我正在维护一个用户管理模块。有个User类,典型的Java Bean风格:

java 复制代码
// Java Bean - 繁琐的样板代码
public class User {
    private String id;
    private String name;
    private int age;
    private String email;

    // 构造函数
    public User(String id, String name, int age, String email) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.email = email;
    }

    // Getter和Setter(20行+)
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    // ... 还有age和email的getter/setter

    // equals() hashCode() toString()(30行+)
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        User user = (User) o;
        return age == user.age &&
               Objects.equals(id, user.id) &&
               Objects.equals(name, user.name) &&
               Objects.equals(email, user.email);
    }
    // ... hashCode()和toString()的实现
}

整个类100行出头,但真正有意义的只有4个字段定义。其余都是样板代码!

当同事用Kotlin重写时,我震惊了:

kotlin 复制代码
// Kotlin Data Class - 简洁到极致
data class User(
    val id: String,
    val name: String,
    val age: Int,
    val email: String
)

仅仅5行代码,却自动拥有:

  • ✅ 构造函数
  • ✅ Getter(val自动生成)
  • ✅ equals() / hashCode()
  • ✅ toString()
  • ✅ copy()(额外福利)

这就是Kotlin的魔法!今天我们就来揭开类与对象的神秘面纱。

类的定义与实例化

最简单的类

在Kotlin中,定义一个类非常简单:

kotlin 复制代码
// 空类
class Empty

// 实例化(注意:无需new关键字)
val obj = Empty()

**与Java的区别**:Kotlin实例化对象时不需要`new`关键字,直接调用类名即可。

带属性的类

kotlin 复制代码
class Person {
    var name: String = ""
    var age: Int = 0
}

// 使用
val person = Person()
person.name = "Alice"
person.age = 25
println("${person.name} is ${person.age} years old")

但这种写法还不够优雅,我们需要构造函数!

构造函数:主构造函数与次构造函数

主构造函数

Kotlin的主构造函数直接写在类头中:

kotlin 复制代码
class Person(val name: String, val age: Int)

// 实例化时必须提供参数
val person = Person("Alice", 25)
println(person.name)  // Alice

主构造函数的参数可以是:

声明方式 说明 是否生成属性 可变性
name: String 仅构造函数参数 -
val name: String 只读属性 不可变
var name: String 可变属性 可变
kotlin 复制代码
class Person(
    name: String,           // 仅参数,类内不可访问
    val id: String,         // 只读属性
    var age: Int            // 可变属性
) {
    // name无法在此处使用
    val greeting = "Hello, my ID is $id"

    fun haveBirthday() {
        age++  // 可以修改
    }
}

初始化块(init)

如果需要在构造时执行逻辑,使用init块:

kotlin 复制代码
class Person(val name: String, var age: Int) {
    val nameLength: Int

    init {
        // 构造时执行
        println("Initializing person: $name")
        require(age >= 0) { "Age cannot be negative" }
        nameLength = name.length
    }

    init {
        // 可以有多个init块,按顺序执行
        println("Name length: $nameLength")
    }
}

val person = Person("Alice", 25)
// 输出:
// Initializing person: Alice
// Name length: 5

次构造函数

如果需要多个构造方式,使用次构造函数:

kotlin 复制代码
class Person(val name: String, var age: Int) {
    var email: String = ""

    // 次构造函数必须调用主构造函数
    constructor(name: String, age: Int, email: String) : this(name, age) {
        this.email = email
    }
}

// 两种构造方式
val person1 = Person("Alice", 25)
val person2 = Person("Bob", 30, "bob@example.com")

**最佳实践**:优先使用主构造函数+默认参数,而非多个次构造函数。这样代码更简洁!

kotlin 复制代码
// 推荐:使用默认参数
class Person(
    val name: String,
    var age: Int,
    var email: String = ""
)

val person1 = Person("Alice", 25)
val person2 = Person("Bob", 30, "bob@example.com")

属性:字段、Getter与Setter

属性的本质

Kotlin的属性不是简单的字段,而是字段+访问器(getter/setter)的组合:

kotlin 复制代码
class Rectangle(var width: Int, var height: Int) {
    // 计算属性(只有getter,无backing field)
    val area: Int
        get() = width * height

    // 带setter的属性
    var isLarge: Boolean = false
        get() = area > 100
        set(value) {
            field = value
            println("isLarge set to $value")
        }
}

val rect = Rectangle(10, 20)
println(rect.area)        // 200 (调用getter)
rect.width = 15           // 修改width(调用setter)
println(rect.area)        // 300 (重新计算)

自定义访问器

kotlin 复制代码
class Person(val firstName: String, val lastName: String) {
    // 自定义getter
    val fullName: String
        get() = "$firstName $lastName"

    // 带backing field的属性
    var age: Int = 0
        set(value) {
            require(value >= 0) { "Age cannot be negative" }
            field = value  // field是backing field的引用
        }
}

val person = Person("Alice", "Smith")
println(person.fullName)  // "Alice Smith"

person.age = 25  // ✅ OK
person.age = -5  // ❌ 抛出异常

延迟初始化属性

有时属性无法在构造时初始化,可以使用lateinitlazy

kotlin 复制代码
// lateinit: 稍后初始化(仅限var)
class MyTest {
    lateinit var database: Database

    fun setup() {
        database = Database.connect()
    }

    fun test() {
        // 使用前必须初始化,否则抛异常
        database.query("SELECT * FROM users")
    }
}

// lazy: 延迟计算(仅限val)
class Heavy {
    val expensiveData: String by lazy {
        println("Computing expensive data...")
        "Result"  // 仅第一次访问时计算
    }
}

val obj = Heavy()
println("Object created")
println(obj.expensiveData)  // 此时才计算
println(obj.expensiveData)  // 使用缓存结果
// 输出:
// Object created
// Computing expensive data...
// Result
// Result

成员函数

类中的函数称为成员函数(方法):

kotlin 复制代码
class Calculator {
    // 无参数函数
    fun sayHello() {
        println("Hello from Calculator")
    }

    // 带参数和返回值
    fun add(a: Int, b: Int): Int {
        return a + b
    }

    // 单表达式函数
    fun multiply(a: Int, b: Int) = a * b

    // 访问属性
    var lastResult: Int = 0

    fun addAndStore(a: Int, b: Int): Int {
        lastResult = a + b
        return lastResult
    }
}

val calc = Calculator()
calc.sayHello()
println(calc.add(3, 5))        // 8
println(calc.multiply(4, 6))   // 24
calc.addAndStore(10, 20)
println(calc.lastResult)       // 30

中缀函数(infix)

使成员函数支持中缀调用:

kotlin 复制代码
class Point(val x: Int, val y: Int) {
    // 中缀函数:单个参数,用infix修饰
    infix fun moveTo(other: Point): Point {
        return Point(other.x, other.y)
    }
}

val p1 = Point(0, 0)
val p2 = Point(10, 20)

// 两种调用方式
val p3 = p1.moveTo(p2)    // 普通调用
val p4 = p1 moveTo p2     // 中缀调用(更自然)

数据类(Data Class):样板代码杀手

为什么需要数据类

回到开头的例子,Java中定义一个数据类需要大量样板代码。Kotlin的data class自动生成:

  • equals() / hashCode()
  • toString()
  • copy()
  • componentN()(解构)
kotlin 复制代码
data class User(
    val id: String,
    val name: String,
    var age: Int,
    val email: String
)

val user1 = User("001", "Alice", 25, "alice@example.com")
val user2 = User("001", "Alice", 25, "alice@example.com")

// 自动生成的方法
println(user1 == user2)           // true (equals)
println(user1)                    // User(id=001, name=Alice, age=25, email=alice@example.com)

// copy: 创建副本并修改部分属性
val user3 = user1.copy(age = 26)
println(user3)                    // User(id=001, name=Alice, age=26, email=alice@example.com)

// 解构声明
val (id, name, age, email) = user1
println("$name is $age years old")


**数据类限制**: 1. 主构造函数必须至少有一个参数 2. 主构造函数参数必须标记为`val`或`var` 3. 不能是`abstract`、`open`、`sealed`或`inner`

数据类最佳实践

kotlin 复制代码
// ✅ 推荐:使用val保证不可变性
data class Point(val x: Int, val y: Int)

// ✅ 推荐:使用默认参数
data class User(
    val id: String,
    val name: String,
    val age: Int = 0,
    val email: String = ""
)

// ❌ 避免:数据类中包含复杂逻辑
data class User(val name: String) {
    fun validateEmail() { ... }  // 不推荐
}

// ✅ 推荐:数据类保持简单,逻辑放到其他类
data class User(val name: String, val email: String)
class UserValidator {
    fun validate(user: User): Boolean { ... }
}

对象声明(Object):单例模式的完美实现

单例对象

Java中实现单例很繁琐,Kotlin用object关键字一步搞定:

kotlin 复制代码
// Kotlin单例
object DatabaseConfig {
    const val HOST = "localhost"
    const val PORT = 5432
    var username: String = "admin"

    fun connect(): String {
        return "Connecting to $HOST:$PORT as $username"
    }
}

// 直接使用对象名调用
println(DatabaseConfig.connect())
DatabaseConfig.username = "root"

对比Java实现:

java 复制代码
// Java单例(需要10+行代码)
public class DatabaseConfig {
    private static final DatabaseConfig INSTANCE = new DatabaseConfig();

    private DatabaseConfig() {}

    public static DatabaseConfig getInstance() {
        return INSTANCE;
    }

    public void connect() { ... }
}

// 使用
DatabaseConfig.getInstance().connect();

对象表达式(匿名对象)

类似Java的匿名内部类:

kotlin 复制代码
interface ClickListener {
    fun onClick()
}

fun setListener(listener: ClickListener) {
    listener.onClick()
}

// 使用对象表达式
setListener(object : ClickListener {
    override fun onClick() {
        println("Clicked!")
    }
})

// 如果接口只有一个方法,可以用Lambda简化
fun setListener2(listener: () -> Unit) {
    listener()
}
setListener2 { println("Clicked!") }

对象用作返回值

kotlin 复制代码
fun getConfig(type: String) = when (type) {
    "dev" -> object {
        val host = "localhost"
        val port = 8080
    }
    "prod" -> object {
        val host = "example.com"
        val port = 443
    }
    else -> throw IllegalArgumentException()
}

val config = getConfig("dev")
println("${config.host}:${config.port}")  // localhost:8080

伴生对象(Companion Object):类级别成员

什么是伴生对象

Kotlin没有static关键字,使用伴生对象实现类似功能:

kotlin 复制代码
class User(val name: String) {
    companion object {
        // 类似Java的静态成员
        const val MIN_AGE = 0
        const val MAX_AGE = 150

        fun create(name: String): User {
            return User(name)
        }

        fun validateAge(age: Int): Boolean {
            return age in MIN_AGE..MAX_AGE
        }
    }
}

// 通过类名直接访问
println(User.MIN_AGE)              // 0
val user = User.create("Alice")    // 工厂方法
println(User.validateAge(200))     // false

伴生对象的名字

kotlin 复制代码
class User {
    companion object Factory {
        fun create(): User = User()
    }
}

// 两种访问方式
val user1 = User.create()          // 省略名字
val user2 = User.Factory.create()  // 使用名字

伴生对象实现接口

伴生对象可以实现接口,这在某些设计模式中很有用:

kotlin 复制代码
interface Factory<T> {
    fun create(): T
}

class User(val name: String) {
    companion object : Factory<User> {
        override fun create(): User {
            return User("Default")
        }
    }
}

// 使用
val factory: Factory<User> = User
val user = factory.create()

伴生对象扩展

甚至可以为伴生对象添加扩展函数:

kotlin 复制代码
class User(val name: String) {
    companion object {
        // 空的伴生对象
    }
}

// 扩展伴生对象
fun User.Companion.fromJson(json: String): User {
    // 解析JSON
    return User("Parsed")
}

// 调用
val user = User.fromJson("{\"name\":\"Alice\"}")

实战:构建用户管理系统

让我们综合运用所学知识,构建一个完整的用户管理系统:

kotlin 复制代码
// 数据类:用户信息
data class User(
    val id: String,
    val name: String,
    var age: Int,
    val email: String,
    var isActive: Boolean = true
) {
    // 计算属性
    val displayName: String
        get() = if (isActive) name else "$name (Inactive)"

    // 成员函数
    fun deactivate() {
        isActive = false
    }

    fun haveBirthday() {
        age++
    }

    companion object {
        // 常量
        const val MIN_AGE = 18
        const val MAX_AGE = 100

        // 工厂方法
        fun createGuest(): User {
            return User(
                id = "guest-${System.currentTimeMillis()}",
                name = "Guest",
                age = 18,
                email = "guest@example.com",
                isActive = false
            )
        }

        // 验证
        fun isValidAge(age: Int): Boolean {
            return age in MIN_AGE..MAX_AGE
        }
    }
}

// 单例:用户管理器
object UserManager {
    private val users = mutableMapOf<String, User>()

    fun addUser(user: User): Boolean {
        if (user.id in users) {
            return false
        }
        users[user.id] = user
        return true
    }

    fun getUser(id: String): User? = users[id]

    fun removeUser(id: String): Boolean {
        return users.remove(id) != null
    }

    fun getAllActiveUsers(): List<User> {
        return users.values.filter { it.isActive }
    }

    fun getUserCount(): Int = users.size
}

// 使用示例
fun main() {
    // 创建用户
    val user1 = User("001", "Alice", 25, "alice@example.com")
    val user2 = User.createGuest()

    // 添加到管理器
    UserManager.addUser(user1)
    UserManager.addUser(user2)

    println("Total users: ${UserManager.getUserCount()}")

    // 修改用户
    user1.haveBirthday()
    println("${user1.name} is now ${user1.age} years old")

    // 使用copy创建变体
    val user3 = user1.copy(id = "003", name = "Bob", age = 30)
    UserManager.addUser(user3)

    // 停用用户
    user2.deactivate()

    // 查询活跃用户
    val activeUsers = UserManager.getAllActiveUsers()
    println("Active users:")
    activeUsers.forEach { user ->
        println("  - ${user.displayName}")
    }

    // 解构用户信息
    val (id, name, age, email) = user1
    println("User details: $name ($age), $email")
}

输出:

sql 复制代码
Total users: 2
Alice is now 26 years old
Active users:
  - Alice
  - Bob
User details: Alice (26), alice@example.com

常见问题

Q1: 主构造函数参数什么时候用val/var?

规则:

  • 需要在类中访问 :使用val(不可变)或var(可变)
  • 仅用于初始化:不加修饰符
kotlin 复制代码
class Person(
    name: String,        // ❌ 类中无法访问
    val id: String,      // ✅ 可以访问,不可修改
    var age: Int         // ✅ 可以访问和修改
) {
    init {
        println(name)    // ❌ 编译错误
        println(id)      // ✅ OK
        age++            // ✅ OK
    }
}

Q2: 什么时候用数据类,什么时候用普通类?

数据类适用于:

  • 主要用于存储数据
  • 需要equals/hashCode/toString
  • 需要copy功能
  • 例如:DTO、Model、配置类

普通类适用于:

  • 包含复杂业务逻辑
  • 需要继承或被继承
  • 单例对象
  • 例如:Service、Manager、Controller
kotlin 复制代码
// ✅ 数据类:纯数据
data class UserDto(val id: String, val name: String)

// ✅ 普通类:有逻辑
class UserService {
    fun authenticate(user: UserDto): Boolean { ... }
}

Q3: lateinit和lazy有什么区别?

特性 lateinit lazy
适用属性 var val
初始化时机 手动初始化 首次访问时
类型限制 非空类型(不能是原始类型) 任意类型
线程安全 是(默认)
使用场景 依赖注入、测试setup 重量级对象、单例
kotlin 复制代码
class Example {
    // lateinit: 稍后手动初始化
    lateinit var database: Database
    fun init() {
        database = Database()
    }

    // lazy: 首次访问时自动初始化
    val config: Config by lazy {
        loadConfig()
    }
}

Q4: 伴生对象和object单例有什么区别?

kotlin 复制代码
// object: 顶层单例
object AppConfig {
    val version = "1.0"
}
// 使用:AppConfig.version

// companion object: 类的伴生对象
class User {
    companion object {
        val DEFAULT_AGE = 18
    }
}
// 使用:User.DEFAULT_AGE

// 区别:
// 1. 伴生对象属于类,可以访问类的私有成员
// 2. 伴生对象可以实现接口
// 3. object是独立的单例对象

总结

本文介绍了Kotlin面向对象编程的核心特性:

1. 类与构造函数

  • 主构造函数:写在类头,简洁直观
  • 次构造函数:必须调用主构造函数
  • init块:构造时执行初始化逻辑
  • 最佳实践:优先使用主构造函数+默认参数

2. 属性

  • 属性 = 字段 + getter/setter
  • 自定义访问器实现计算属性和验证
  • lateinit:延迟初始化var属性
  • lazy:延迟计算val属性

3. 数据类

  • data class消灭样板代码
  • 自动生成equals/hashCode/toString/copy
  • 支持解构声明
  • 保持数据类简单,逻辑分离

4. 对象声明

  • object:完美实现单例模式
  • 对象表达式:替代匿名内部类
  • 伴生对象:实现类级别成员(替代static)

5. 关键设计原则

  • 优先使用不可变性(val)
  • 数据与逻辑分离
  • 利用Kotlin特性减少样板代码
  • 遵循单一职责原则

练习题

练习1:图书管理系统

创建一个图书管理系统,要求:

kotlin 复制代码
// TODO: 实现Book数据类
// 包含:isbn, title, author, price, publishYear

// TODO: 实现BookStore单例
// 功能:
// - addBook(book): Boolean
// - findByAuthor(author): List<Book>
// - findByPriceRange(min, max): List<Book>
// - getMostExpensiveBook(): Book?

fun main() {
    // 测试代码
    val book1 = Book("978-0134685991", "Effective Java", "Joshua Bloch", 45.99, 2017)
    val book2 = Book("978-0132350884", "Clean Code", "Robert Martin", 42.50, 2008)

    BookStore.addBook(book1)
    BookStore.addBook(book2)

    val martinBooks = BookStore.findByAuthor("Robert Martin")
    println(martinBooks)
}

练习2:学生成绩系统

创建学生成绩系统:

kotlin 复制代码
// TODO: 实现Student类
// 主构造函数:id, name
// 属性:grades (Map<String, Int>,存储各科成绩)
// 计算属性:averageGrade
// 方法:addGrade(subject, grade), getGrade(subject)

// TODO: 实现StudentManager伴生对象
// 功能:
// - 工厂方法创建学生
// - 验证成绩范围(0-100)
// - 获取成绩等级(A/B/C/D/F)

fun main() {
    val student = Student.create("001", "Alice")
    student.addGrade("Math", 95)
    student.addGrade("English", 88)

    println("Average: ${student.averageGrade}")
    println("Math grade: ${Student.getGradeLevel(95)}")
}

练习3:配置管理器

实现一个配置管理器:

kotlin 复制代码
// TODO: 实现Config数据类
// 包含:host, port, username, password
// 提供copy方法创建不同环境配置

// TODO: 实现Environment枚举
// 包含:DEV, TEST, PROD

// TODO: 实现ConfigManager单例
// 功能:
// - 存储各环境配置
// - 切换当前环境
// - 获取当前配置

fun main() {
    val devConfig = Config("localhost", 8080, "dev", "dev123")
    val prodConfig = devConfig.copy(
        host = "prod.example.com",
        port = 443,
        username = "admin"
    )

    ConfigManager.setConfig(Environment.DEV, devConfig)
    ConfigManager.setConfig(Environment.PROD, prodConfig)

    ConfigManager.switchEnvironment(Environment.PROD)
    println(ConfigManager.getCurrentConfig())
}

系列文章导航:


如果这篇文章对你有帮助,欢迎点赞、收藏、分享!有任何问题或建议,欢迎在评论区留言讨论。让我们一起学习,一起成长!

也欢迎访问我的个人主页发现更多宝藏资源

相关推荐
笔夏2 小时前
【安卓学习之webRTC】学习相关资料
android·学习·webrtc
_李小白2 小时前
【Android 美颜相机】第三天:初识GPUImageView
android·数码相机
行稳方能走远2 小时前
Android java 学习笔记3
android·java
Larry_Yanan2 小时前
Qt安卓开发(二)摄像头打开
android·开发语言·数据库·c++·qt·ui
Sammyyyyy2 小时前
PHP 8.6 新特性预览,更简洁的语法与更严谨的类型控制
android·php·android studio
撩得Android一次心动2 小时前
Android Lifecycle 全面解析:掌握生命周期管理的艺术(1)
android·java·kotlin·lifecycle
毕设源码-朱学姐3 小时前
【开题答辩全过程】以 基于安卓的家校交流平台为例,包含答辩的问题和答案
android