嵌套类与内部类 (Nested & Inner Classes)
在 Kotlin 中,在一个类内部定义另一个类是常见的做法。但与 Java 默认行为不同,Kotlin 对“持有外部类引用”这件事持极其保守的态度,以防止意外的内存泄漏。
嵌套类 (Nested Classes)
默认情况下,在类内部定义的类是嵌套类。 它不持有外部类的引用,等同于 Java 中的 static 内部类。
kotlin
class Outer {
private val bar: Int = 1
class Nested {
fun foo() = 2
// fun accessOuter() = bar // ❌ 编译错误:无法访问外部类成员
}
}
// 实例化无需外部类实例
val demo = Outer.Nested()最佳实践
除非你明确需要访问外部类的成员,否则请始终保持默认的嵌套类状态。这能避免隐式的引用持有,对 GC 更友好。
内部类 (Inner Classes)
如果你需要访问外部类的成员,必须添加 inner 关键字。 此时它变成了内部类,持有外部类的引用。
kotlin
class Outer {
private val bar: Int = 1
inner class Inner {
fun foo() = bar // ✅ 可以访问外部成员
}
}
// 实例化必须依赖外部类实例
val demo = Outer().Inner()访问外部类引用:this@Label
在内部类中,如果遇到命名冲突,或者需要显式引用外部类实例,使用 this@OuterName 语法。
kotlin
class Outer {
val name = "Outer"
inner class Inner {
val name = "Inner"
fun printNames() {
println(this.name) // 访问 Inner.name
println(this@Outer.name) // 访问 Outer.name
}
}
}匿名内部类 (Anonymous Inner Classes)
对象表达式 (object : Interface { ... }) 创建的也是内部类,它会捕获定义它的作用域中的变量。
详细内容请参考 对象表达式。
内存泄漏警示 重要
在 Android 开发中,inner class 是内存泄漏的重灾区。
kotlin
class MyActivity : Activity() {
// ❌ 危险:这是一个 inner class
inner class MyHandler : Handler() { ... }
}由于 MyHandler 隐式持有 MyActivity 的引用,如果 Handler 的消息队列中有延迟消息,那么 Activity 即使销毁了也无法被回收。
修正方案:使用默认的嵌套类(即删除 inner),如果需要引用 Activity,请通过弱引用 (WeakReference) 显式传递。
总结
| 特性 | Kotlin (默认) | Kotlin (inner) | Java (默认) | Java (static) |
|---|---|---|---|---|
| 外部类引用 | ❌ 无 | ✅ 有 | ✅ 有 | ❌ 无 |
| 内存风险 | 低 | 高 | 高 | 低 |
| 实例化 | Outer.Nested() | Outer().Inner() | new Outer().new Inner() | new Outer.Nested() |