包、导入与可见性

包、导入和可见性共同决定一段 Kotlin 代码的命名空间和 API 边界。对 Java 开发者来说,Kotlin 的包和导入整体很熟悉,但顶层声明、文件级 privateinternal 这些特性会改变代码组织方式。

包声明

Kotlin 文件可以从包声明开始:

package org.example.user

fun printUserName(name: String) {
    println(name)
}

class User(val name: String)

这个文件中的顶层函数和类都属于 org.example.user 包:

  • org.example.user.printUserName
  • org.example.user.User

如果不写包声明,文件内容属于默认包。实际项目中不建议使用默认包,因为它会让大型项目的命名和导入变得混乱。

包名与目录

Kotlin 不强制包名和目录结构完全一致。也就是说,下面这种情况在语言层面可以成立:

src/main/kotlin/foo/User.kt
package com.example.user

但工程实践中仍建议保持一致,因为 IDE、构建工具、代码搜索和团队习惯都会假设二者一致。

默认导入

每个 Kotlin 文件都会默认导入一些常用包,例如:

  • kotlin.*
  • kotlin.collections.*
  • kotlin.io.*
  • kotlin.ranges.*
  • kotlin.sequences.*
  • kotlin.text.*

JVM 平台还会默认导入:

  • java.lang.*
  • kotlin.jvm.*

所以你可以直接使用 StringIntListprintln 等常见类型和函数。

普通导入与星号导入

导入单个声明:

import org.example.user.User

导入某个作用域下的所有可访问声明:

import org.example.user.*

Kotlin 的 import 不只用于类,也可以导入:

  • 顶层函数。
  • 顶层属性。
  • object 中的函数和属性。
  • 枚举常量。

示例:

import kotlin.math.max

fun main() {
    println(max(10, 20))
}

Java 中静态方法通常需要 static import;Kotlin 没有 static import 关键字,因为顶层函数和对象成员可以直接通过普通 import 导入。

导入别名

命名冲突时使用 as

import org.example.Message
import org.test.Message as TestMessage

val a = Message()
val b = TestMessage()

这在同时使用多个库、多个模块存在同名类时很有用。

顶层声明的可见性

Kotlin 允许顶层函数、属性和类:

private fun normalizeName(name: String): String =
    name.trim()

顶层 private 的作用域是“当前文件”,不是当前包。这个特性很适合把文件内部辅助函数隐藏起来,不需要创建私有工具类。

可见性修饰符总览

Kotlin 常见可见性修饰符:

修饰符 顶层声明 类成员
public 任意地方可见,默认值 任意地方可见,默认值
private 当前文件可见 当前类内部可见
protected 不可用于顶层声明 当前类和子类可见
internal 当前模块可见 当前模块可见

public 是默认值,通常不需要显式写出。

internal

internal 表示当前模块可见:

internal class TokenParser

“模块”通常由构建系统决定,例如一个 Gradle source set、一个 Maven module 或一次编译单元。它不是 Java 的 package-private。

Java 对比:

  • Java 的默认可见性是包内可见。
  • Kotlin 没有 Java 那种 package-private。
  • Kotlin 的 internal 是模块级边界,更适合多模块项目隐藏实现。

类成员可见性

open class Base {
    public val a = 1
    private val b = 2
    protected val c = 3
    internal val d = 4
}

注意:

  • private 成员只在当前类内部可见。
  • protected 成员在子类可见,但不像 Java 那样还对同包可见。
  • internal 成员在当前模块可见。

构造函数可见性

class User private constructor(val name: String) {
    companion object {
        fun create(name: String): User {
            require(name.isNotBlank())
            return User(name)
        }
    }
}

私有构造函数常用于工厂方法、单例控制或限制实例创建。

实践建议

  • 文件内部辅助函数优先使用顶层 private,不必创建 Utils 类。
  • 模块内部实现类使用 internal,公共 API 保持小而清晰。
  • 不要滥用星号导入;团队应交给 IDE 格式化规则统一处理。
  • Java 迁移时不要把 package-private 机械翻译成 internal,先确认边界是“包”还是“模块”。
  • protected 在 Kotlin 中比 Java 更严格,不提供同包访问能力。

参考

  • 官方包与导入:https://kotlinlang.org/docs/packages.html
  • 官方可见性修饰符:https://kotlinlang.org/docs/visibility-modifiers.html