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


原始标题:Kotlin Nested and Inner Classes

» 下一篇: Future 的同步处理