一、类的声明与主构造函数
// 最简形式:无参主构造函数
class User
// 带参数的主构造函数(直接在类名后声明)
class User(val name: String, var age: Int)
// 完整写法(constructor关键字可省略)
class User public constructor(name: String) {
val userName: String = name
}
关键点:
-
val参数 → 生成只读属性 + getter -
var参数 → 生成可读写属性 + getter/setter - 普通参数 → 仅作为构造参数使用,不生成属性
二、init 初始化块
init 块在主构造函数执行时立即运行,按声明顺序执行,常用于参数校验和初始化逻辑。
class Student(val name: String, val age: Int) {
val studentId: String
init {
// 参数校验
require(name.isNotBlank()) { "姓名不能为空" }
require(age in 6..30) { "年龄必须在6-30之间" }
// 复杂初始化
studentId = "S${System.currentTimeMillis()}"
println("学生 $name 初始化完成")
}
// 可以有多个init块,按顺序执行
init {
println("第二个init块执行")
}
}
对比Java:
// Java写法
public class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
// 校验和初始化逻辑写在这里
}
}
Kotlin 的 init 块将初始化逻辑与构造参数分离,更清晰。
三、次构造函数
使用 constructor 关键字定义,必须直接或间接委托给主构造函数。
class Person(val name: String, val age: Int) {
// 委托给主构造函数
constructor(name: String) : this(name, 0) {
println("次构造函数:只有姓名")
}
// 委托给另一个次构造函数
constructor() : this("Unknown", 0) {
println("次构造函数:无参数")
}
}
// 使用
val p1 = Person("Alice", 25) // 主构造
val p2 = Person("Bob") // 次构造1
val p3 = Person() // 次构造2
执行顺序:主构造 → init块 → 次构造函数体
class Demo(name: String) {
init { println("1. init块执行") }
constructor(name: String, age: Int) : this(name) {
println("2. 次构造函数体执行")
}
}
// 调用 Demo("A", 18) 输出顺序:1 → 2
四、属性声明与 getter/setter
主构造函数中的属性可以自定义访问器:
class Rectangle(val width: Int, val height: Int) {
// 自定义getter(每次计算)
val area: Int
get() = width * height
// 带field关键字的setter(支持幕后字段)
var perimeter: Int = 0
set(value) {
field = value // field是幕后字段,避免递归
println("周长已更新为 $value")
}
}
幕后字段 field:在自定义getter/setter中代表实际存储值的字段,仅在需要时使用。
五、属性初始化时机(容易踩坑)
class MyClass(val name: String) {
val upperName = name.uppercase() // ✅ 直接用构造参数
val greeting: String
get() = "Hello $name" // ✅ 每次调用时计算
// ❌ 错误:lateinit属性不能与getter一起用
// lateinit var message: String
// get() = field ?: "default"
// ✅ lateinit用于延迟初始化(var,非空类型)
lateinit var dynamicData: String
// ✅ by lazy用于懒加载(val,只读)
val heavyObject: HeavyClass by lazy {
HeavyClass() // 首次访问时才初始化
}
}
六、可见性与构造函数的组合
// 主构造函数私有(工厂模式)
class Singleton private constructor(val id: Int) {
companion object {
fun create(id: Int) = Singleton(id)
}
}
// 构造参数可见性控制
class User internal constructor(val name: String) // 模块内可见
// 属性可见性独立控制
class Product(val name: String) {
var price: Double = 0.0
private set // setter私有,外部只读
}
七、完整实战示例
// 用户实体类(数据类+主构造)
data class User(
val id: Long,
val username: String,
private var _email: String
) {
// 带校验的setter
var email: String
get() = _email
set(value) {
require(value.contains("@")) { "邮箱格式不正确" }
_email = value
}
// 次构造:便捷创建
constructor(username: String) : this(0, username, "$username@default.com")
init {
require(username.length >= 3) { "用户名至少3个字符" }
}
// 派生属性
val isPremium: Boolean
get() = id > 1000
}
// 使用
fun main() {
val user = User(1001, "alice", "alice@mail.com")
user.email = "new@mail.com" // 调用setter校验
// user._email // ❌ 私有不可访问
val guest = User("guest") // 次构造
println(guest.email) // guest@default.com
}
关键对比总结
| 特性 | Kotlin | Java |
|---|---|---|
| 构造参数声明 | 直接在类名后 | 在类体内声明 |
| 初始化逻辑 |
init块 |
构造方法内 |
| 属性+构造合一 | ✅ val/var参数 |
❌ 需手动赋值 |
| 多个构造 | 主构造+次构造(必须委托) | 多个构造方法(可互相调用) |
| 默认构造 | 自动生成无参构造 | 没写构造才有默认 |
常见陷阱提醒 🚨
-
次构造函数不能声明属性:
constructor(val name: String)❌ 这是错误的,参数不能加val/var - init块在次构造函数体之前执行:别把依赖次构造参数的逻辑写在init里
- data class不能有无参主构造:data class主构造至少要有一个参数
-
lateinit var不能是基本类型:Int、Boolean等不能用lateinit(可用
by lazy替代)