1. 简介
本文将深入探讨 Kotlin 中四种创建嵌套类(nested class)和内部类(inner class)的方式。对于熟悉 Java 的开发者来说,这些概念并不陌生,但在 Kotlin 中有其独特的语法和行为差异,掌握它们有助于写出更简洁、封装性更强的代码。
2. 与 Java 的快速对比
如果你对 Java 中的嵌套类 比较熟悉,可以通过下表快速建立 Kotlin 与 Java 之间的对应关系:
Kotlin | Java |
---|---|
内部类 (Inner Classes) | 非静态嵌套类 (Non-Static Nested Classes) |
局部类 (Local Classes) | 局部类 (Local Classes) |
匿名对象 (Anonymous Objects) | 匿名类 (Anonymous Classes) |
嵌套类 (Nested Classes) | 静态嵌套类 (Static Nested Classes) |
⚠️ 注意:虽然功能上相似,但 Kotlin 和 Java 在实现细节和限制上存在差异,不能完全等同。此表仅作为理解参考。
3. 内部类(Inner Classes)
✅ 使用 inner
关键字声明的类称为内部类。
这类类的关键特性是:
- 可以直接访问外部类的所有成员,包括
private
成员 - 必须依赖外部类的实例才能创建,不能独立存在
来看一个例子:在 Computer
类中定义一个 HardDisk
内部类:
class Computer(val model: String) {
inner class HardDisk(val sizeInGb: Int) {
fun getInfo() = "Installed on ${this@Computer} with $sizeInGb GB"
}
}
📌 注意:
- 使用
this@Computer
可以显式引用外部类实例,类似于 Java 中的Computer.this
- 如果不加限定,
this
指向的是当前内部类实例
测试用例如下:
@Test
fun givenHardDisk_whenGetInfo_thenGetComputerModelAndDiskSizeInGb() {
val hardDisk = Computer("Desktop").HardDisk(1000)
assertThat(hardDisk.getInfo())
.isEqualTo("Installed on Computer(model=Desktop) with 1000 GB")
}
❌ 踩坑提醒:如果尝试通过 HardDisk()
直接构造实例,编译器会报错 —— 必须先有外部类实例!
4. 局部内部类(Local Inner Classes)
✅ 在函数体或作用域块内定义的类称为局部类。
它的特点包括:
- 作用域仅限于声明它的方法或代码块
- 可以访问并修改所在函数中的变量(无需
final
或“实际上的 final”) - 每次执行到该位置都会重新定义这个类
扩展之前的 Computer
类,添加 powerOn
方法,并在其内部定义一个 Led
类:
fun powerOn(): String {
var defaultColor = "Blue"
class Led(val color: String) {
fun blink(): String {
return "blinking $color"
}
fun changeDefaultPowerOnColor() {
defaultColor = "Violet"
}
}
val powerLed = Led("Green")
log.debug("defaultColor is $defaultColor")
powerLed.changeDefaultPowerOnColor()
log.debug("defaultColor changed inside Led class to $defaultColor")
return powerLed.blink()
}
输出结果为:
[main] DEBUG c.b.n.Computer - defaultColor is Blue
[main] DEBUG c.b.n.Computer - defaultColor changed inside Led class to Violet
💡 优势:相比 Java,Kotlin 允许局部类修改外部变量,无需绕过“effectively final”的限制,写起来更自然。
5. 匿名对象(Anonymous Objects)
✅ 用于一次性实现接口或抽象类,无需命名类名。
Kotlin 匿名对象相较于 Java 匿名类的优势:
- ✅ 支持实现多个接口
- ✅ 可以包含额外的方法(不仅仅是接口方法)
- ✅ 同样可以修改外部作用域的变量
首先定义一个开关接口:
interface Switcher {
fun on(): String
}
然后在 powerOn
方法中使用对象表达式(object expression)创建匿名对象:
fun powerOn(): String {
var defaultColor = "Blue"
class Led(val color: String) {
fun blink() = "blinking $color"
}
val powerLed = Led("Green")
val powerSwitch = object : Switcher {
override fun on(): String {
return powerLed.blink()
}
fun changeDefaultPowerOnColor() {
defaultColor = "Yellow"
}
}
powerSwitch.changeDefaultPowerOnColor()
log.debug("defaultColor changed inside powerSwitch anonymous object to $defaultColor")
return powerSwitch.on()
}
输出:
...
[main] DEBUG c.b.n.Computer - defaultColor changed inside powerSwitch anonymous object to Yellow
📌 补充说明:
- 每次执行
object : ...
都会创建一个新的实例 - 若目标类型是 SAM(Single Abstract Method)接口,也可用 lambda 替代,例如:
Runnable { /* do something */ }
6. 嵌套类(Nested Classes)
✅ 不带 inner
关键字的类就是嵌套类,相当于 Java 中的静态嵌套类。
主要特性:
- ❌ 无法访问外部类的实例成员
- ✅ 可以访问外部类的
companion object
成员
示例:
class Computer(val model: String) {
class MotherBoard(val manufacturer: String) {
fun getInfo()
= "Made by $manufacturer - $originCountry - ${getBuiltDate()}"
}
companion object {
const val originCountry = "China"
fun getBuiltDate(): String {
return "2018-07-15T01:44:25.38Z"
}
}
}
测试代码:
@Test
fun givenMotherboard_whenGetInfo_thenGetInstalledAndBuiltDetails() {
val motherBoard = Computer.MotherBoard("MotherBoard Inc.")
assertThat(motherBoard.getInfo())
.isEqualTo("Made by MotherBoard Inc. - China - 2018-07-15T01:44:25.38Z")
}
📌 关键点:
- 创建
MotherBoard
实例时不需要先创建Computer
实例 - 可以自由使用
companion object
中的常量和方法
7. 总结
本文系统梳理了 Kotlin 中四种嵌套结构的使用场景和区别:
类型 | 是否需要外部实例 | 访问外部成员 | 典型用途 |
---|---|---|---|
内部类 (inner ) |
✅ 是 | ✅ 全部 | 需要强绑定外部状态的组件 |
局部类 | ✅ 是 | ✅ 可修改变量 | 方法内部复杂逻辑封装 |
匿名对象 | ✅ 是 | ✅ 可修改变量 | 一次性接口实现、回调处理 |
嵌套类 (无 inner ) |
❌ 否 | ❌ 实例成员 | 工具类、配置类、与外部解耦的组件 |
合理利用这些机制,可以让代码结构更清晰、封装更彻底。特别是 Kotlin 在局部类和匿名对象中取消了“effectively final”的限制,大大提升了灵活性。
完整可运行示例代码已托管至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-lang-oop