Skip to content

嵌套类与内部类 (Nested & Inner Classes)

源:Nested and 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()