在 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()
}
六、注意事项
- 避免内存泄漏
内部类持有外部类引用时,若内部类生命周期长于外部类(如异步回调),需手动解绑。 - 私有成员访问
内部类可直接访问外部类的私有成员,嵌套类不可。 - 作用域限制
嵌套类和内部类的作用域受外部类限制,无法脱离外部类单独存在。
总结
- 优先使用嵌套类:当不需要访问外部类实例时,减少内存开销。
- 谨慎使用内部类:明确需要操作外部类实例时再用,注意生命周期管理。
- 匿名内部类灵活但需谨慎:默认持有外部类引用,适合短生命周期场景。