1. 简介
Kotlin 是一门构建在 Java 虚拟机(JVM)之上的语言,因此它必须遵循 JVM 的所有规则 —— 包括可见性修饰符的相关机制。
然而,在实际使用中,Kotlin 对这些修饰符的实现方式与 Java 存在一些细微差别,尤其是在编译器处理和代码结构设计方面。本文将对比 Java 和 Kotlin 在可见性控制方面的异同点,帮助有经验的开发者快速掌握 Kotlin 的可见性规则。
2. 可见性修饰符详解
可见性修饰符用于控制程序中的元素(如类、函数、变量等)能否被其他代码访问。它们可以作用于不同层级的代码结构,比如文件顶层、类内部等。由于作用范围的不同,其具体行为也会略有差异,初学者容易混淆。
2.1. Public 可见性
public
是最直观的修饰符,也是使用频率最高的。它表示 没有任何访问限制,任何地方都可以访问该元素。
✅ **在 Kotlin 中,如果不显式声明可见性修饰符,默认就是 public
**。这与 Java 不同,Java 中默认是 package-private。
- 如果应用于顶层声明(如顶层类、函数或属性),则整个项目中都可以访问。
- 如果应用于类的成员(如内部类、方法、属性),那么只要能访问该类的地方,就能访问这些成员。
示例代码如下:
class Public {
val i = 1
fun doSomething() {
}
}
在这个例子中:
- 类
Public
全局可访问; - 属性
i
和方法doSomething()
只要能访问Public
类,就能访问。
2.2. Private 可见性
private
是另一个高频使用的修饰符,含义正好与 public
相反:仅在当前作用域内可见。
⚠️ 在 Kotlin 中,这意味着:
- 顶层的
private
元素只能在同一个文件中访问; - 类成员的
private
元素只能在该类内部访问。
这比 Java 更加灵活,因为 Kotlin 支持在一个文件中定义多个顶层声明。
示例代码如下:
private class Private {
private val i = 1
private fun doSomething() {
}
}
说明:
- 类
Private
只能在当前文件中被访问; - 属性
i
和方法doSomething()
只能在类Private
内部访问。
2.3. Protected 可见性
protected
表示 仅允许当前类及其子类访问。
⚠️ 注意!这与 Java 有所不同:
- Java 中
protected
还允许同一包下的其他类访问; - Kotlin 中则严格限定为 当前类及其子类,无论是否在同一包下。
来看一个示例:
// 文件1: package one
package one
open class A {
protected val i = 1
}
// 文件2: package two
package two
class B : A() {
fun getValue(): Int {
return i // ✅ 合法,因为是子类
}
}
但如果尝试在同一个包但不是子类中访问:
// 文件3: package one
package one
class C {
fun getValue(): Int {
val a = A()
return a.i // ❌ 编译错误,不是子类
}
}
再来看跨包且非子类的情况:
// 文件4: package two
package two
class D {
fun getValue(): Int {
val a = A()
return a.i // ❌ 编译错误,既不是子类也不在同一包
}
}
此外,⚠️ **在 Kotlin 中,如果重写一个 protected
成员,默认仍然是 protected
**。你可以手动将其改为 public
,但这是少数需要显式指定 public
的场景之一。
2.4. Package-Private / Default 可见性(Java 特有)
Java 中有一个特殊的可见性级别叫 package-private(或称 default),即不加任何修饰符时的默认访问级别。它的含义是:
- 同一包下可访问;
- 不同包下不可访问,即使是子类也不行。
❌ Kotlin 当前并不支持这种可见性修饰符。官方给出的理由是:
- 它本质上没有提供真正的封装保护;
- 任何人都可以通过定义相同包名的方式访问你的代码,甚至从别的 jar 包中也能做到。
2.5. Internal 可见性(Kotlin 独有)
这是 Kotlin 新增的一个可见性修饰符,Java 中没有直接对应的概念。
✅ internal
的含义是:
同一模块(module)内可见。
这里的“模块”通常指一个编译单元,比如一个 jar 包或者一个 Gradle module。
这个特性在大型项目中非常有用,特别是在设计 API 时:
- 将接口定义为
public
; - 实现类也设为
public
; - 所有辅助逻辑、工具类等标记为
internal
,这样外部模块就无法直接访问这些内部实现。
示例代码如下:
package com.example.modifiers
internal class Internal {
}
class Public {
internal val i = 1
internal fun doSomething() {
}
}
解释:
- 类
Internal
只能在当前模块中访问; - 属性
i
和方法doSomething()
也只能在当前模块中访问,尽管它们所在的类Public
是公开的。
💡 这种机制非常适合做 API 封装,避免外部模块绕过接口直接调用内部实现,增强系统的稳定性和安全性。
3. 总结
在这篇文章中,我们详细探讨了 Kotlin 中的可见性修饰符体系,并与 Java 进行了比较。
虽然大部分规则与 Java 类似,但 Kotlin 做了一些关键性的优化和扩展:
- 默认可见性为
public
,更符合现代语言习惯; - 引入了
internal
修饰符,极大提升了模块化开发的能力; protected
的语义更加严格,消除了 Java 中因包结构导致的安全隐患。
对于有经验的 Java 开发者来说,理解这些差异有助于写出更清晰、更安全的 Kotlin 代码。