跳到主要内容 Kotlin 类、对象和接口:定义类继承结构 | 极客日志
Kotlin java
Kotlin 类、对象和接口:定义类继承结构 详细讲解了 Kotlin 中类继承结构的核心概念。内容包括接口的定义、多接口实现及默认方法冲突解决;open、final、abstract 等修饰符的使用机制及其与 Java 的区别;可见性修饰符 public、internal、protected、private 的作用范围及编译差异;嵌套类与内部类的区别及序列化注意事项;以及密封类 sealed 如何限制继承层级以确保类型安全。通过对比 Java 语法,阐述了 Kotlin 在继承体系上的改进与最佳实践。
XiaoPingzi 发布于 2025/2/7 更新于 2026/4/20 1 浏览
Kotlin 中的接口 在 Kotlin 中,接口用于定义行为规范。下图展示了声明一个拥有名为 click 的单抽象方法的接口,所有实现该接口的非抽象类都要提供这一方法的实现。
interface Clickable {
fun click ()
}
Kotlin 使用冒号 : 代替了 Java 中的 extends 和 implements 关键字。和 Java 一样,一个类可实现多个接口但只能继承一个类。
class Button : Clickable {
override fun click () {
println("I was clicked" )
}
}
Override 修饰符与默认实现 在 Kotlin 中使用 override 修饰符是强制要求的,这会避免先写出实现方法后添加抽象方法造成的意外重写。
接口的方法可以有一个默认实现。在 Java 8 中需要你在这样的实现上标注 default 关键字,而 Kotlin 没有特殊的注解,只需要提供一个方法体。
interface Clickable {
fun click ()
fun showOff () {
println("Clicking is cool" )
}
}
现在再定义一个 Focusable 接口,其中同样实现了一个 showOff 方法:
interface Focusable {
fun focusOn () {}
fun showOff () {
println("Focusing is cool" )
}
}
当你的类实现这两个接口时,如果它们有相同签名的方法(如 showOff),不会使用任何一个实现,编译器会要求你提供你自己的实现来消除歧义。
class MyButton : Clickable , Focusable {
override fun click () {
println("MyButton clicked" )
}
override fun showOff () {
println("MyButton showing off" )
}
}
要调用一个继承的实现,可以使用 super 关键字,但是选择一个特定实现的语法是不同的。在 Java 中是 Clickable.super.showOff(),而在 Kotlin 中是 super<Clickable>.showOff()。
在 Java 中实现包含方法体的接口 Kotlin 1.0 是以 Java 6 为目标设计的,并不支持接口中的默认方法,因此它会把每个带默认方法的接口编译成一个普通接口和一个将方法体作为静态函数的类的结合体。但在现代 Kotlin 版本中,这已完全支持。
open、final、abstract 修饰符:默认为 final 在 Java 中允许你创建任意类的子类并重写任意方法,除非显式调用了 final 关键字进行标注。对基类进行修改会导致子类不正确的行为,这就是所谓的脆弱的基类问题。基类的任何修改都有可能导致子类出现与其之外的行为改变。
在 Java 中类和方法默认都是 open 的,在 Kotlin 中类和方法默认都是 final 的。
如果你想允许创建一个类的子类,则要使用 open 修饰符来标示这个类,还要给每一个可以重写的属性和方法添加 open 修饰符。
open class Base {
open fun doSomething () {
println("Base doing something" )
}
}
class Derived : Base () {
override fun doSomething () {
super .doSomething()
println("Derived doing something" )
}
}
如果你重写了一个基类或者接口的成员,重写的成员同样默认是 open 的。如果你想阻止你的类的子类重写你的实现,可以显式将重写的成员标为 final。
class Derived2 : Base () {
final override fun doSomething () {
println("Final override" )
}
}
open 类和智能转换 类默认 final 使得在大量场景中的智能转换成为可能。我们之前提到智能转换只能在进行类型检查后没有改变过的变量上起作用,对于一个类来说,这意味着智能转换只能在 val 类型并且没有自定义访问器的类属性上使用。这个前提意味着属性必须是 final 的,否则如果一个子类可以重写属性并定义一个自定义的访问器将会打破智能转换的关键前提。
在 Kotlin 中,可以将一个类声明为 abstract 的,这种类不能被实例化。它通常包含一些没有实现并且必须在子类重写的抽象成员。抽象成员始终是 open 的,所以不需要显式使用 open。
类中访问修饰符 相关成员 评注 final 不能被重写 类中成员默认使用 open 可以被重写 需要明确表明 abstract 必须被重写 只能在抽象类中使用,抽象成员不能有实现 override 重写父类或接口中的成员 如果没有用 final 表明,重写的成员默认是开放的
可见性修饰符:默认为 public Kotlin 中和 Java 几乎一样,但是默认情况不一样,Kotlin 默认是 public。而且提供了一个新修饰符:internal(只在模块内部可见)。一个模块就是一组一起编译的 Kotlin 文件,有可能是一个 IDEA 模块、一组使用调用 Ant 任务进行编译的文件等等。
Kotlin 还允许在顶层声明中使用 private,包括类、函数、属性,这些声明就会只在声明它们的文件中可见。
可见性修饰符 类成员 顶层声明 public 所有地方可见 所有地方可见 internal 模块中可见 模块中可见 protected 子类中可见 无 private 类中可见 文件中可见
Kotlin 禁止从 public 函数引用低可见的类型。通用规则:类的基础类型和类型参数列表中用到的所有类,或者函数的签名都有与这个类或者函数本身相同的可见性。
注意 protected 修饰符在 Java 和 Kotlin 的不同。在 Java 中可以从同一个包中访问 protected 成员,但 Kotlin 不允许,在 Kotlin 中只允许其成员在类和它的子类中可见。
注意类的扩展函数不能访问它的 private 和 protected 成员。
Kotlin 的可见性修饰符和 Java Kotlin 的 public、protected、private 修饰符在编译成 Java 字节码时会被保留。唯一的例外是 private 类:这种情况会被编译成包私有声明。
一个模块通常会由多个包组成,并且不同模块可能会包含来自同一个包的声明,因此 internal 修饰符在字节码中会变成 public,这就解释了为什么有时能从 Java 代码中访问一些你不能从 Kotlin 中访问的东西。类的 internal 成员名字会被破坏。
在 Kotlin 中一个外部类不能看到其内部/嵌套类中的 private 成员。
内部类和嵌套类:默认是嵌套类 在 Kotlin 中可以在另一个类中声明一个类,这样做在封装一个辅助类或者把一些代码放到靠近它被使用的地方时非常有用。但区别于 Java,Kotlin 的嵌套类不能访问外部类的实例,除非你特别地作出了要求。
class MyButton {
interface State {
fun serialize () : String
}
class ButtonState : State {
override fun serialize () : String = "state"
}
}
可以看到我们试图返回一个实现了 State 接口的 ButtonState,在 Kotlin 中可行,在 Java 中这样的写法会报错,因为在 Java 中 ButtonState 类隐式地存储了它的外部 MyButton 的引用,而 MyButton 是不能被序列化的,它的引用破坏了 ButtonState 的序列化。要修复它需要声明 ButtonState 类为 static,这个嵌套类就会删除包围它的类的隐式引用。Kotlin 中没有显式修饰符的嵌套类和 Java 中的 static 嵌套类是一样的。
要把它变成一个内部类来持有一个外部类的引用的话要使用 inner 修饰符。
类 A 在另一个类 B 声明 在 Java 中 在 Kotlin 中 嵌套类(不存储外部类的引用) static class A class A 内部类(存储外部类的引用) class A inner class A
在 Kotlin 中引用外部类实例的语法也和 Java 不同,需要用 this@Outer 从 Inner 类访问 Outer 类。
class Outer {
val x = 10
inner class Inner {
fun printX () {
println(this @Outer .x)
}
}
}
密封类:定义受限的类继承结构 回顾在 Kotlin 基础篇章中的例子,在处理枚举或有限集合时,我们往往需要在 when 表达式中处理所有可能的子类。
sealed class Expr
class Num (val value: Int ) : Expr()
class Sum (val left: Expr, val right: Expr) : Expr()
在 when 表达式中处理所有可能的子类固然方便,但是必须提供一个 else 分支来处理没有任何其他分支能匹配的情况。总是不得不添加一个默认分支很不方便,更重要的是如果你添加了一个新的子类,编译器并不能发现有地方改变了,如果你忘记添加新分支那么就会有 bug。
Kotlin 为此提供了一个解决方案:sealed 类。
为父类添加 sealed 修饰符,对可能创建的子类做出严格的限制,所有的直接子类必须嵌套在父类中。
sealed class Expr {
class Num (val value: Int ) : Expr()
class Sum (val left: Expr, val right: Expr) : Expr()
}
密封类是不能在类外部拥有子类的,如果你在 when 表达式中处理所有 sealed 类的子类,就不需要再提供默认分支。sealed 修饰符隐含的这个类是一个 open 类,不再需要显式地添加 open 修饰符。
我们不能声明 sealed 接口,因为编译器不能保证任何人都不能在 Java 代码中实现这个接口。
在 Kotlin 1.0 中,sealed 功能是相当严格的,所有子类必须是嵌套的,且子类不能创建为 data 类。1.1 中解除了这些限制并允许在同一文件的任何位置定义 sealed 类的子类。
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online