1. 概述

本文将深入探讨 Kotlin 中的继承机制,重点解析 open 关键字的作用。

我们会先从面向对象设计中“继承”的哲学理念谈起,接着分析 open 关键字如何影响 Kotlin 中的类、方法和属性。最后还会讨论在使用 Spring 等企业级框架时,open 与框架动态代理之间的兼容性问题——这可是实际项目中经常踩坑的地方。

2. 继承的设计原则

在 Java 中,默认情况下类和方法都是可被继承或重写的(除非显式标注为 final)。这意味着你可以随意扩展任何类或覆盖任意方法。

然而,继承关系会带来强耦合,一旦父类行为发生变化,子类可能随之崩溃。Joshua Bloch 在《Effective Java》中明确指出:

要么精心设计并文档化以支持继承,要么干脆禁止继承。

✅ 正确的做法是:默认封闭(final),仅在有明确需求时才开放扩展。而且一旦决定允许继承,就必须清晰地说明重写某个方法会产生什么影响。

但 Java 的默认行为恰恰相反——默认开放,靠 final 来限制。而 Kotlin 则反其道而行之,更符合现代软件设计的趋势。

3. 类层次结构中的 open

⚠️ Kotlin 中所有类默认都是 final 的,无法被继承。

例如以下代码:

class Try
class Success : Try()

编译器会直接报错:

Kotlin: This type is final, so it cannot be inherited from

要让一个类可被继承,必须显式加上 open 关键字:

open class Try
class Success : Try()

此时才能正常继承。

注意事项:

  • open 不具有传递性:即使 Success 继承了 open class TrySuccess 本身仍是 final 的。
  • ❌ 若想让 Success 也能被继承,必须给它也加上 open
  • 🔍 从字节码层面看,未加 open 的 Kotlin 类会被编译成 Java 中的 final class

我们可以通过 javap 验证这一点:

$ kotlinc Inheritance.kt 
$ javap -c -p -v com.baeldung.inheritance.Sealed 

输出结果中会出现:

public final class com.baeldung.inheritance.Sealed

可见 Kotlin 默认的封闭性是在 JVM 层面真正落实的。

4. 方法与属性的重写

与类类似,Kotlin 中的方法和属性默认也是 final 的,哪怕所在类已经是 open

举个例子:

open class Try {
    fun isSuccess(): Boolean = false
}
class Success : Try() {
    override fun isSuccess(): Boolean = true
}

这段代码无法通过编译:

Kotlin: 'isSuccess' in 'Try' is final and cannot be overridden

查看字节码也能确认该方法是 final 的:

public final boolean isSuccess();
   flags: (0x0011) ACC_PUBLIC, ACC_FINAL

如何正确重写?

必须在父类中用 open 显式声明可重写:

open class Try {
    open fun isSuccess(): Boolean = false
}
class Success : Try() {
    override fun isSuccess(): Boolean = true
}

属性同理:

open class Try {
    open val value: Any? = null
}
class Success(override val value: Any?) : Try()

上面这种写法利用了主构造函数参数直接覆盖父类属性,简洁高效。

子类能否继续重写?

✅ 默认可以!因为使用 override 的方法/属性会隐式变为 open,允许进一步继承。

如果你不希望子类继续重写,应显式加上 final

open class Success(override val value: Any?) : Try() {
    final override fun isSuccess(): Boolean = true
}

这样,Success 的子类就无法再重写 isSuccess() 了。

5. 与企业框架的兼容性问题

Spring、Hibernate 等 JVM 框架大量依赖运行时代理(如 CGLIB 动态代理)来实现 AOP、懒加载等功能。这些代理技术通常通过继承目标类来生成子类。

因此,如果目标类是 final 的(比如普通的 Kotlin 类),就会导致代理创建失败。

常见场景举例

@Service
@Transactional
open class UserService {

    open fun register(u: User) {
        // 注册逻辑
    }
}

为了让 Spring 能为其创建代理,我们必须:

  • 类加 open
  • 所有需要被代理的方法也要加 open

❌ 这种方式虽然可行,但非常繁琐且容易遗漏,严重影响开发体验。

解决方案:Kotlin-all-open 插件

JetBrains 提供了 Kotlin-allopen 插件,专门解决这个问题。

✅ 它的作用是:自动为带有特定注解的类及其成员添加 open 修饰符,无需手动标注。

使用步骤:

  1. build.gradle.kts 中应用插件:
plugins {
    kotlin("plugin.allopen") version "1.9.0"
}

allOpen {
    annotations("org.springframework.stereotype.Service", 
                "javax.persistence.Entity",
                "org.springframework.transaction.annotation.Transactional")
}
  1. 之后就可以写“看似 final”的代码,实际会被自动 open 化:
@Service
@Transactional
class UserService {  // 不再需要 open

    fun register(u: User) {  // 也不需要 open
        // 注册逻辑
    }
}

编译时,插件会自动识别 @Service@Transactional,并将该类及方法标记为 open,完美适配 Spring 的代理机制。

📌 小贴士:IntelliJ IDEA 创建 Spring Boot + Kotlin 项目时,默认已集成 all-open 插件,省去手动配置烦恼。

6. 总结

  • ✅ Kotlin 默认一切皆 final,强调“安全封闭”,优于 Java 的默认开放策略。
  • ✅ 要支持继承,必须显式使用 open 关键字(类、方法、属性均适用)。
  • override 成员默认是 open 的,若需封闭应加 final
  • ⚠️ 与 Spring/Hibernate 集成时,推荐使用 Kotlin-allopen 插件,避免手动添加大量 open
  • 💡 设计类时牢记:除非明确需要继承,否则保持 final 是最佳实践

示例代码已上传至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin-2


原始标题:Open Keyword in Kotlin