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 代码。


原始标题:Visibility Modifiers in Kotlin