Kotlin: Kotlin类与对象详解
最后更新:2026-08-26
Kotlin 的类设计哲学:主构造器 + 属性一步到位,init 块替代 Java 的构造器体——Charlie 用 3 行代码定义的 Order 类比 Java 的 30 行还强。
1. 你将学到
- 主构造器 + 属性一步到位
- 块:验证逻辑
- 可见性: / / /
- 延迟初始化: 与
- Charlie 实战:设计 Order / OrderItem / Customer 领域类
2. 一个架构师的真实故事
(1) 痛点:Java 构造器地狱
Charlie 在 Java 中定义 Order 类需要 3 个构造器(无参、必填、全参),加上 getter/setter/验证逻辑,一个领域类就 80+ 行代码。
(2) Kotlin 主构造器的解法
KOTLIN
// Java: 80+ lines for one domain class
// Kotlin: 3 lines with validation
class Order(val id: String, var status: String, val total: BigDecimal) {
init { require(total >= BigDecimal.ZERO) { "Total must be non-negative" } }
}
主构造器 + 属性 + init 块三合一,80 行 Java → 3 行 Kotlin。
3. 主构造器与属性
(1) 主构造器一步到位
KOTLIN
// Primary constructor with properties in one line
class Order(val id: String, var status: String, val total: Double)
// Equivalent Java would be 20+ lines
(2) 构造器参数 vs 属性
KOTLIN
// val/var in constructor = property
class Order(val id: String, // read-only property
var status: String, // mutable property
total: Double) // just constructor param, not a property
// Usage
val order = Order("ORD-001", "PENDING", 299.99)
println(order.id) // OK - val property
order.status = "PAID" // OK - var property
// order.total // ERROR - not a property
(3) 主构造器 vs 次构造器
KOTLIN
class Order(val id: String, var status: String, val total: Double) {
// Secondary constructor MUST delegate to primary
constructor(id: String) : this(id, "PENDING", 0.0)
// Another secondary constructor
constructor(id: String, total: Double) : this(id, "PENDING", total)
}
val order1 = Order("ORD-001") // Secondary
val order2 = Order("ORD-002", 299.99) // Secondary
val order3 = Order("ORD-003", "CONFIRMED", 1_500.00) // Primary
(4) 构造器方式对比
| 维度 | Java | Kotlin |
|---|---|---|
| 定义属性+构造 | 分开写 | 主构造器一行搞定 |
| 多构造器 | 独立定义 | 次构造器必须委托主构造 |
| 参数变属性 | 手动赋值 | / 自动 |
| 默认值 | 方法重载 | 默认参数 |
4. init 块
块在主构造器执行后立即运行,用于验证和初始化逻辑。
(1) 基本用法
KOTLIN
class Order(val id: String, var status: String, val total: Double) {
init {
require(total >= 0.0) { "Total must be non-negative, got $total" }
require(id.startsWith("ORD-")) { "Order ID must start with ORD-" }
}
// Multiple init blocks execute in order
init {
println("Order $id created with total \$$total USD")
}
}
(2) init 块执行顺序
KOTLIN
class Example {
val a = println("1: property initialization")
init {
println("2: first init block")
}
val b = println("3: property initialization")
init {
println("4: second init block")
}
}
// Output: 1, 2, 3, 4 (declaration order)
5. 可见性修饰符
(1) 四种可见性
KOTLIN
class OrderProcessor {
// public (default): visible everywhere
fun process(order: Order) { ... }
// private: visible inside this class only
private fun validate(order: Order) { ... }
// protected: visible in this class and subclasses
protected fun calculateTax(order: Order) { ... }
// internal: visible within the same module
internal fun report() { ... }
}
(2) 可见性对比表
| 修饰符 | 类内 | 子类 | 同模块 | 全局 |
|---|---|---|---|---|
| ✅ | ✅ | ✅ | ✅ | |
| ✅ | ✅ | ✅ | ❌ | |
| ✅ | ✅ | ❌ | ❌ | |
| ✅ | ❌ | ❌ | ❌ |
(3) Java vs Kotlin 可见性差异
| 维度 | Java | Kotlin |
|---|---|---|
| 默认可见性 | package-private | |
| 模块可见 | 无 | |
| 包可见 | package-private | 无(用 internal 替代) |
| 顶层声明 | 只有 public | public / internal / private |
6. 延迟初始化
(1) lateinit var
KOTLIN
class OrderService {
// lateinit: promise to initialize before use
lateinit var repository: OrderRepository
fun init(repo: OrderRepository) {
repository = repo
}
fun process(order: Order) {
// Access before init throws UninitializedPropertyAccessException
repository.save(order)
}
}
(2) by lazy
KOTLIN
class OrderProcessor {
// lazy: thread-safe, initialized on first access
val cache: OrderCache by lazy {
println("Initializing cache...")
OrderCache(maxSize = 10_000)
}
// lazy with custom lock mode
val heavyResource by lazy(LazyThreadSafetyMode.PUBLICATION) {
loadHeavyResource()
}
}
(3) lateinit vs by lazy 对比
| 维度 | ||
|---|---|---|
| 类型 | (可变) | (只读) |
| 初始化时机 | 手动赋值 | 首次访问时 |
| 线程安全 | 否 | 是(默认) |
| 可空性 | 非空声明 | 非空声明 |
| 未初始化访问 | 运行时异常 | 不会发生 |
| 适用场景 | DI 框架注入 | 计算成本高的属性 |
7. 类关系图
classDiagram
class Order {
+val id: String
+var status: String
+val total: Double
+val items: List~OrderItem~
+fun addItem(item: OrderItem)
}
class OrderItem {
+val sku: String
+val quantity: Int
+val unitPrice: Double
+fun subtotal: Double
}
class Customer {
+val id: String
+val name: String
+val email: String?
+val address: Address?
}
class Address {
+val street: String
+val city: String
+val country: String
}
Order --> OrderItem : contains
Order --> Customer : belongs to
Customer --> Address : has
8. 完整示例:OrderProcessor 领域模型
KOTLIN
// ============================================
// OrderProcessor - Domain Model
// Feature: Order, OrderItem, Customer with init validation
// ============================================
import java.math.BigDecimal
import java.math.RoundingMode
class Address(val street: String, val city: String, val country: String) {
override fun toString(): String = "$street, $city, $country"
}
class Customer(val id: String, val name: String, val email: String?) {
var address: Address? = null
fun getDisplayEmail(): String = email ?: "no-email"
override fun toString(): String = "Customer($id, $name, ${getDisplayEmail()})"
}
class OrderItem(val sku: String, val quantity: Int, val unitPrice: BigDecimal) {
init {
require(quantity > 0) { "Quantity must be positive, got $quantity" }
require(unitPrice >= BigDecimal.ZERO) { "Price must be non-negative" }
}
val subtotal: BigDecimal
get() = unitPrice.multiply(BigDecimal(quantity)).setScale(2, RoundingMode.HALF_UP)
}
class Order(
val id: String,
var status: String,
private val _items: MutableList`<OrderItem>` = mutableListOf()
) {
init {
require(id.startsWith("ORD-")) { "Order ID must start with ORD-" }
}
val items: List`<OrderItem>` get() = _items.toList()
val total: BigDecimal
get() = _items.fold(BigDecimal.ZERO) { acc, item -> acc.add(item.subtotal) }
fun addItem(item: OrderItem) {
_items.add(item)
}
val itemCount: Int get() = _items.size
// Lazy computed tax
val tax by lazy {
total.multiply(BigDecimal("0.08")).setScale(2, RoundingMode.HALF_UP)
}
override fun toString(): String = "Order($id, $status, ${itemCount} items, \$$total USD)"
}
fun main() {
val customer = Customer("CUST-001", "Alice", "alice@example.com").also {
it.address = Address("123 Main St", "New York", "US")
}
println(customer)
println("Address: ${customer.address}")
val order = Order("ORD-001", "PENDING")
order.addItem(OrderItem("SKU-WIDGET", 3, BigDecimal("9.99")))
order.addItem(OrderItem("SKU-GADGET", 1, BigDecimal("149.99")))
order.addItem(OrderItem("SKU-DOOHICKEY", 5, BigDecimal("4.50")))
println(order)
println("Subtotal: \$$${order.total} USD")
println("Tax: \$$${order.tax} USD")
println("Grand Total: \$$${order.total.add(order.tax)} USD")
order.status = "CONFIRMED"
println("Status updated: ${order.status}")
}
输出:
TEXT
📖 仅展示
Customer(CUST-001, Alice, alice@example.com)
Address: 123 Main St, New York, US
Order(ORD-001, PENDING, 3 items, $199.42 USD)
Subtotal: $$199.42 USD
Tax: $$15.95 USD
Grand Total: $$215.37 USD
Status updated: CONFIRMED
❓ 常见问题
Q 主构造器和次构造器可以共存吗?
A 可以,但次构造器必须用 委托到主构造器。推荐用默认参数替代次构造器。
Q init 块可以有多个吗?
A 可以,多个 init 块按声明顺序执行。推荐合并为一个以避免混淆。
Q lateinit 能用于基本类型吗?
A 不能。lateinit 只能用于非基本类型(对象类型),因为基本类型有默认值。Int/Double 等用 或可空类型 + 默认值。
Q by lazy 的初始化是线程安全的吗?
A 默认是线程安全的(SYNCHRONIZED 模式)。如果确定单线程访问,可用 提升性能。
Q internal 可见性在 Maven/Gradle 多模块项目中如何生效?
A internal 限制在同一 Gradle 模块或 Maven 模块内可见。不同模块即使同一项目也不能访问 internal 成员。
Q 为什么 Kotlin 没有 Java 的 package-private?
A Kotlin 用 (模块级可见性)替代 package-private。包级可见性在 Java 中常被误用,模块级更符合现代项目结构。
📖 小节
- 主构造器 + / 一步定义属性,消灭 Java 的 getter/setter 样板
- 块替代 Java 构造器体,用于验证和初始化
- 四种可见性:(默认)/ / /
- 适用于 DI 框架注入, 适用于延迟计算
- 属性 backing field 用 + 公开只读视图的模式实现防御性拷贝
- 默认参数优先于次构造器,保持构造器简洁
📝 作业
- 基础题(难度⭐):定义一个 类(id: String, name: String, price: Double),用 init 块验证 price >= 0。提示:
- 进阶题(难度⭐⭐):设计一个 类,用 注入 ,用 初始化缓存。提示: +
- 挑战题(难度⭐⭐⭐):实现一个完整的 Order 领域模型,包含 Order/OrderItem/Customer,所有验证逻辑在 init 块中,items 使用防御性拷贝。提示:参考第 8 节完整示例