Kotlin 内部类与嵌套类

314 阅读2分钟

在 Kotlin 中,‌内部类(Inner Class) ‌ 和 ‌嵌套类(Nested Class) ‌ 是两种不同的类嵌套方式,主要区别在于是否持有外部类的引用。以下是它们的核心区别和使用场景:


一、核心区别

特性嵌套类(Nested Class)内部类(Inner Class)
默认行为不持有外部类的引用(类似 Java 的静态嵌套类)持有外部类的引用(需用 inner 关键字声明)
访问外部类成员无法访问外部类实例的属性和方法可访问外部类实例的属性和方法(包括私有成员)
实例化方式直接通过外部类名访问,无需外部类实例需先创建外部类实例,再通过实例访问
内存开销更轻量(无额外引用)有额外引用(可能影响内存回收)

二、语法与示例

1. 嵌套类(Nested Class)

class Outer {
    private val outerProperty = "Outer Property"

    // 嵌套类(默认不持有外部类引用)
    class Nested {
        fun printInfo() {
            // ❌ 无法访问 outerProperty
            println("This is a nested class")
        }
    }
}

// 实例化
val nested = Outer.Nested()
nested.printInfo() // 输出: This is a nested class

2. 内部类(Inner Class)

class Outer {
    private val outerProperty = "Outer Property"

    // 内部类(需用 inner 关键字)
    inner class Inner {
        fun printInfo() {
            // ✅ 可访问外部类的属性和方法
            println("Outer property: $outerProperty")
        }
    }
}

// 实例化
val outer = Outer()
val inner = outer.Inner()
inner.printInfo() // 输出: Outer property: Outer Property

三、使用场景

1. 嵌套类适用场景

  • 工具类或辅助类‌:与外部类逻辑相关,但无需依赖其实例。

    
    class MathUtils {
        companion object {  // 伴生对象也可用于工具方法
            fun add(a: Int, b: Int) = a + b
        }
    
        // 嵌套类用于特定数据结构
        class Vector(val x: Int, val y: Int) {
            fun length() = sqrt(x * x + y * y.toDouble())
        }
    }
    

2. 内部类适用场景

  • 紧密耦合的操作‌:需要直接操作外部类实例。

    class RecyclerView {
        private var adapter: Adapter? = null
    
        // 内部类访问外部类的 adapter
        inner class Adapter {
            fun bindData() {
                println("Binding data to RecyclerView")
                this@RecyclerView.adapter = this
            }
        }
    }
    

四、匿名内部类(对象表达式)

通过 object 表达式创建的匿名类,‌默认持有外部类引用‌(若在外部类内部定义):

class View {
    fun setOnClickListener(listener: OnClickListener) {
        // 触发点击事件时调用
        listener.onClick()
    }
}

class Activity {
    fun initView() {
        val view = View()
        // 匿名内部类(持有 Activity 的引用)
        view.setOnClickListener(object : OnClickListener {
            override fun onClick() {
                println("Clicked in Activity")
            }
        })
    }
}

五、与 Java 的对比

特性Kotlin 嵌套类Kotlin 内部类Java 静态嵌套类Java 非静态内部类
访问外部类实例不可访问可访问(需 inner不可访问可访问
语法默认无 inner需显式声明 inner默认静态默认非静态
内存管理无隐式引用,更安全需注意内存泄漏风险类似 Kotlin 嵌套类类似 Kotlin 内部类

案例

/ TODO 内部类
// 内部类的特点: 内部的类 能访问 外部的类
//              外部的类 能访问 内部的类
class Body(_bodyInfo: String) { // 身体类

    val bodyInfo = _bodyInfo

    fun show() {
        Heart().run()
    }

    // 默认情况下:内部的类 不能访问 外部的类,要增加修饰符inner 成为内部类才可以访问外部类
    inner class Heart { // 心脏类
        fun run() = println("心脏访问身体信息:$bodyInfo")
    }

    inner class Kidney { // 肾脏
        fun work() = println("肾脏访问身体信息:$bodyInfo")
    }

    inner class Hand { // 手
        inner class LeftHand { // 左手
            fun run() = println("左手访问身体信息:$bodyInfo")
        }

        inner class RightHand { // 右手
            fun run() = println("右手访问身体信息:$bodyInfo")
        }
    }
}

// TODO 嵌套类
// 默认情况下:就是嵌套类关系
// 嵌套类特点:外部的类 能访问 内部的嵌套类
//           内部的类 不能访问 外部类的成员
class Outer {

    val info: String  = "OK"

    fun show() {
        Nested().output()
    }

    class Nested {

        fun output() = println("嵌套类")

    }
}

// TODO 90.Kotlin语言的嵌套类学习
fun main() {
    // 内部类:
    Body("isOK").Heart().run()
    Body("isOK").Hand().LeftHand().run()
    Body("isOK").Hand().RightHand().run()

    // 嵌套类:
    Outer.Nested().output()

}

六、注意事项

  1. 避免内存泄漏
    内部类持有外部类引用时,若内部类生命周期长于外部类(如异步回调),需手动解绑。
  2. 私有成员访问
    内部类可直接访问外部类的私有成员,嵌套类不可。
  3. 作用域限制
    嵌套类和内部类的作用域受外部类限制,无法脱离外部类单独存在。

总结

  • 优先使用嵌套类‌:当不需要访问外部类实例时,减少内存开销。
  • 谨慎使用内部类‌:明确需要操作外部类实例时再用,注意生命周期管理。
  • 匿名内部类灵活但需谨慎‌:默认持有外部类引用,适合短生命周期场景。