引言:那个让我重写的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 // ❌ 抛出异常
延迟初始化属性
有时属性无法在构造时初始化,可以使用lateinit或lazy:
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())
}
系列文章导航:
- 👉 上一篇: 控制流与函数:从if表达式到Lambda的进化之路
如果这篇文章对你有帮助,欢迎点赞、收藏、分享!有任何问题或建议,欢迎在评论区留言讨论。让我们一起学习,一起成长!
也欢迎访问我的个人主页发现更多宝藏资源