从 https://kotlinlang.org 翻译过来

Basic syntax overview

Package definition and imports

包声明需置于源文件顶部:

package my.demo

import kotlin.text.*

// ...

Kotlin 不要求目录结构与包名一致:源文件可任意存放于文件系统中。

Program entry point

Kotlin 应用的入口点是 main 函数:

fun main() {
    println("Hello world!")
}

另一种形式的 main 函数支持接收可变长度的字符串参数:

fun main(args: Array<String>) {
    println(args.contentToString())
}

  • print 函数:将参数输出到标准输出(无换行):

    print("Hello ")
    print("world!") // 输出结果:Hello world!
    

  • println 函数:打印参数并添加换行符(后续输出自动换行):

    println("Hello world!")
    println(42) // 输出结果:Hello world!(换行)42(换行)
    

Read from the standard input

readln() 函数用于读取标准输入,接收用户输入的整行内容并返回字符串:

// 打印输入提示信息
println("Enter any word: ")

// 读取用户输入并存储(示例输入:Happiness)
val yourWord = readln()

// 打印包含输入内容的结果
print("You entered the word: ")
print(yourWord) // 输出结果:You entered the word: Happiness

Functions

  1. 带两个 Int 参数且返回 Int 类型的函数:

    fun sum(a: Int, b: Int): Int {
        return a + b
    }
    

  2. 函数体为表达式(返回类型自动推导):

    fun sum(a: Int, b: Int) = a + b
    

  3. 无有意义返回值的函数(返回 Unit):

    fun printSum(a: Int, b: Int): Unit {
        println("sum of $a and $b is ${a + b}")
    }
    

  4. Unit 返回类型可省略:

    fun printSum(a: Int, b: Int) {
        println("sum of $a and $b is ${a + b}")
    }
    

Variables

Kotlin 中声明变量需以 val 或 var 关键字开头,后跟变量名:

  1. val:声明不可变变量(仅赋值一次,只读,初始化后无法重新赋值):

    // 声明变量 x 并初始化为 5
    val x: Int = 5 // 输出结果:5
    

  2. var:声明可变变量(可在初始化后重新赋值):

    // 声明变量 x 并初始化为 5
    var x: Int = 5
    // 重新赋值为 6
    x += 1 // 输出结果:6
    

  3. 类型推导:Kotlin 支持自动识别变量类型,声明时可省略类型标注:

    // 变量 x 初始化为 5,自动推导为 Int 类型
    val x = 5 // 输出结果:5
    

  4. 延迟初始化:变量必须初始化后才能使用,支持「先声明后初始化」(需显式指定类型):

    // 声明时初始化,无需指定类型
    val x = 5
    // 先声明后初始化,必须指定类型
    val c: Int
    c = 3 // 输出结果:5、3
    

  5. 顶层变量:可在文件顶层声明变量(无需嵌套在类 / 函数中):

    val PI = 3.14
    var x = 0
    
    fun incrementX() {
        x += 1
    }
    // 初始状态:x = 0; PI = 3.14
    // 调用 incrementX() 后:x = 1; PI = 3.14
    

Creating classes and instances

  1. 定义类:使用 class 关键字:

    class Shape
    
  2. 类属性:可在类声明或类体中定义:

    class Rectangle(val height: Double, val length: Double) {
        val perimeter = (height + length) * 2 // 周长(派生属性)
    }
    
  3. 默认构造函数:类声明中列出的参数会自动生成默认构造函数:

    class Rectangle(val height: Double, val length: Double) {
        val perimeter = (height + length) * 2
    }
    
    fun main() {
        val rectangle = Rectangle(5.0, 2.0)
        println("The perimeter is ${rectangle.perimeter}") // 输出:The perimeter is 14.0
    }
    

  4. 类继承:使用冒号(:)声明继承关系。默认情况下类为 final(不可继承),需显式标记 open 才能被继承:

    open class Shape // 可继承的基类
    
    class Rectangle(val height: Double, val length: Double) : Shape() {
        val perimeter = (height + length) * 2
    }

Comments

Kotlin 支持单行注释和多行注释(与大多数现代语言一致):

// 这是单行注释(行尾注释)

/* 这是多行注释
   跨越多行 */

Kotlin 的多行注释支持嵌套:

/* 注释开始
/* 包含嵌套注释 */
注释结束 */

String templates

字符串模板

var a = 1
// 模板中引用简单变量名
val s1 = "a is $a" // 结果:"a is 1"

a = 2
// 模板中嵌入任意表达式(使用 ${})
val s2 = "${s1.replace("is", "was")}, but now is $a" // 结果:"a was 1, but now is 2"

if else

条件表达式

  1. 标准条件语句:

    fun maxOf(a: Int, b: Int): Int {
        if (a > b) {
            return a
        } else {
            return b
        }
    }
    

  2. 条件表达式(if 可作为表达式返回值):

    fun maxOf(a: Int, b: Int) = if (a > b) a else b
    

for

  1. 遍历集合元素:

    val items = listOf("apple", "banana", "kiwifruit")
    for (item in items) {
        println(item) // 依次输出:apple、banana、kiwifruit
    }
    

  2. 按索引遍历:

    val items = listOf("apple", "banana", "kiwifruit")
    for (index in items.indices) {
        println("item at $index is ${items[index]}") 
        // 输出:item at 0 is apple、item at 1 is banana、item at 2 is kiwifruit
    }
    

while

val items = listOf("apple", "banana", "kiwifruit")
var index = 0
while (index < items.size) {
    println("item at $index is ${items[index]}")
    index++ // 索引自增
}

when

fun describe(obj: Any): String =
    when (obj) {
        1          -> "One" // 匹配值为 1
        "Hello"    -> "Greeting" // 匹配字符串 "Hello"
        is Long    -> "Long" // 匹配 Long 类型
        !is String -> "Not a string" // 匹配非 String 类型
        else       -> "Unknown" // 默认匹配(无其他匹配时执行)
    }

Ranges

区间

  1. 检查数值是否在区间内(使用 in 运算符):

    val x = 10
    val y = 9
    if (x in 1..y+1) { // 1..10(闭区间,包含首尾)
        println("fits in range") // 输出:fits in range
    }
    

  2. 检查数值是否在区间外(使用 !in 运算符):

    val list = listOf("a", "b", "c")
    
    if (-1 !in 0..list.lastIndex) { // list.lastIndex = 2,区间 0..2
        println("-1 is out of range") // 输出:-1 is out of range
    }
    if (list.size !in list.indices) { // list.indices = 0..2,list.size = 3
        println("list size is out of valid list indices range, too") // 输出该语句
    }
    

  3. 遍历区间:

    for (x in 1..5) {
        print(x) // 输出:12345
    }
    

  4. 遍历步长区间:

    for (x in 1..10 step 2) { // 步长为 2(1、3、5、7、9)
        print(x) // 输出:13579
    }
    println()
    for (x in 9 downTo 0 step 3) { // 倒序遍历,步长为 3(9、6、3、0)
        print(x) // 输出:9630
    }
    

Collections

  1. 遍历集合:

    for (item in items) {
        println(item)
    }
    

  2. 检查集合是否包含元素(使用 in 运算符):

    when {
        "orange" in items -> println("juicy")
        "apple" in items -> println("apple is fine too") // 若 items 包含 "apple",输出该语句
    }
    

  3. 集合过滤与映射(使用 lambda 表达式):

    val fruits = listOf("banana", "avocado", "apple", "kiwifruit")
    fruits
        .filter { it.startsWith("a") } // 过滤以 "a" 开头的元素(avocado、apple)
        .sortedBy { it } // 按字母排序(apple、avocado)
        .map { it.uppercase() } // 转为大写(APPLE、AVOCADO)
        .forEach { println(it) } // 依次输出:APPLE、AVOCADO
    

Nullable values and null checks

可空值与空检查

当变量可能为 null 时,需显式标记为可空类型(类型名后加 ?):

  1. 返回可空值的函数:

    // 若字符串无法转为整数,返回 null
    fun parseInt(str: String): Int? {
        // ...(实现逻辑)
    }
    
  2. 使用返回可空值的函数:

    fun printProduct(arg1: String, arg2: String) {
        val x = parseInt(arg1)
        val y = parseInt(arg2)
    
        // Using `x * y` yields error because they may hold nulls.
        if (x != null && y != null) {
            // x and y are automatically cast to non-nullable after null check
            println(x * y)
        }
        else {
            println("'$arg1' or '$arg2' is not a number")
        }    
    }

        or

// ...
if (x == null) {
    println("Wrong number format in arg1: '$arg1'")
    return
}
if (y == null) {
    println("Wrong number format in arg2: '$arg2'")
    return
}

// x and y are automatically cast to non-nullable after null check
println(x * y)

Type checks and automatic casts

类型检查与自动类型转换

is 运算符用于检查表达式是否为某个类型的实例。若对不可变局部变量或属性进行了特定类型检查,则无需显式进行类型转换(编译器会自动完成):

fun getStringLength(obj: Any): Int? {
    if (obj is String) {
        // 此分支中 `obj` 会自动转换为 String 类型
        return obj.length
    }

    // 类型检查分支外,`obj` 仍为 Any 类型
    return null
}

另一种写法(提前返回空值):

fun getStringLength(obj: Any): Int? {
    if (obj !is String) return null // 若不是 String 类型,直接返回 null

    // 此分支中 `obj` 自动转换为 String 类型
    return obj.length
}

Keywords and operators

Hard keywords

以下标记始终被解析为关键字,不能用作标识符:

关键字用途说明
as用于类型转换;为导入声明指定别名
as?用于安全类型转换(转换失败返回 null)
break终止循环执行
class声明类
continue跳转到最近外层循环的下一次迭代
do开始 do/while 循环(后置条件循环)
else定义 if 表达式中条件为 false 时执行的分支
false指定布尔类型的「假」值
for开始 for 循环
fun声明函数
if开始 if 表达式
in指定 for 循环中被迭代的对象;作为中缀运算符检查值是否属于某个范围、集合或定义了 contains 方法的实体;在 when 表达式中用于相同目的;标记类型参数为逆变(contravariant)
!in作为运算符检查值不属于某个范围、集合或定义了 contains 方法的实体;在 when 表达式中用于相同目的
interface声明接口
is检查值是否为特定类型;在 when 表达式中用于相同目的
!is检查值不是特定类型;在 when 表达式中用于相同目的
null表示不指向任何对象的常量引用
object同时声明类及其实例(单例 / 伴生对象)
package指定当前文件所属的包
return从最近的外层函数或匿名函数返回
super引用超类的方法 / 属性实现;从次级构造函数调用超类构造函数
this引用当前接收者(Receiver);从次级构造函数调用同类的其他构造函数
throw抛出异常
true指定布尔类型的「真」值
try开始异常处理块
typealias声明类型别名
typeof预留作未来使用
val声明只读属性或局部变量
var声明可变属性或局部变量
when开始 when 表达式(执行给定分支中的一个)
while开始 while 循环(前置条件循环)

Soft keywords

以下标记仅在适用上下文中作为关键字,在其他上下文可作为标识符:

关键字用途说明
by将接口实现委托给另一个对象;将属性访问器的实现委托给另一个对象
catch开始处理特定异常类型的代码块
constructor声明主构造函数或次级构造函数
delegate用作注解的使用位置目标(annotation use-site target)
dynamic在 Kotlin/JS 代码中引用动态类型
field用作注解的使用位置目标
file用作注解的使用位置目标
finally开始在 try 块退出时始终执行的代码块
get声明属性的 getter;用作注解的使用位置目标
import将其他包中的声明导入当前文件
init开始初始化块
param用作注解的使用位置目标
property用作注解的使用位置目标
receiver用作注解的使用位置目标
set声明属性的 setter;用作注解的使用位置目标
setparam用作注解的使用位置目标
value与 class 关键字搭配声明内联类(inline class)
where指定泛型类型参数的约束条件

Modifier keywords

以下标记仅在声明的修饰符列表中作为关键字,在其他上下文可作为标识符:

关键字用途说明
abstract将类或成员标记为抽象
actual在多平台项目中表示平台特定的实现
annotation声明注解类
companion声明伴生对象
const将属性标记为编译时常量
crossinline禁止在传递给内联函数的 lambda 中使用非局部返回
data指示编译器为类生成规范成员(equals()/hashCode()/toString() 等)
enum声明枚举类
expect将声明标记为平台特定,期望在平台模块中提供实现
external将声明标记为在 Kotlin 外部实现(通过 JNI 或 JavaScript 访问)
final禁止覆盖成员
infix允许使用中缀表示法调用函数
inline告诉编译器在调用点内联函数及其传入的 lambda
inner允许从嵌套类引用外部类实例
internal将声明标记为在当前模块内可见
lateinit允许在构造函数外初始化非空属性
noinline关闭传递给内联函数的 lambda 的内联
open允许类被继承或成员被覆盖
operator将函数标记为重载运算符或实现约定
out标记类型参数为协变(covariant)
override将成员标记为覆盖超类成员
private将声明标记为仅在当前类或文件中可见
protected将声明标记为仅在当前类及其子类中可见
public将声明标记为在任何位置可见
reified将内联函数的类型参数标记为在运行时可访问
sealed声明密封类(子类受限的类)
suspend将函数或 lambda 标记为挂起(可用于协程)
tailrec将函数标记为尾递归(允许编译器用迭代替换递归)
vararg允许参数接收可变数量的参数

Special identifiers

特殊标识符

以下标识符由编译器在特定上下文中定义,在其他上下文可作为常规标识符:

标识符用途说明
field在属性访问器内部引用属性的幕后字段(backing field)
it在 lambda 内部隐式引用其参数

Operators and special symbols

Kotlin 支持以下运算符和特殊符号:

符号 / 运算符用途说明
+, -, *, /, %数学运算符;* 也用于将数组传递给 vararg 参数
=赋值运算符;用于指定参数的默认值
+=, -=, *=, /=, %=增强赋值运算符
++, --自增 / 自减运算符
&&, `, !`逻辑「与」「或」「非」运算符(位运算需使用对应的中缀函数替代)
==, !=相等运算符(非基本类型会转换为 equals() 调用)
===, !==引用相等运算符(检查是否指向同一对象)
<, >, <=, >=比较运算符(非基本类型会转换为 compareTo() 调用)
[, ]索引访问运算符(转换为 get 和 set 调用)
!!断言表达式非空(若为空则抛出 NullPointerException)
?.安全调用(接收者非空时调用方法 / 访问属性,为空则返回 null)
?:Elvis 运算符(左值为 null 时取右值)
::创建成员引用或类引用
.., ..<创建范围(.. 为闭区间,..< 为半开区间)
:在声明中分隔名称与类型
?标记类型为可空
->分隔 lambda 表达式的参数与体;分隔函数类型中的参数与返回类型声明;分隔 when 表达式分支的条件与体
@引入注解;引入或引用循环标签;引入或引用 lambda 标签;引用外部作用域的 this 表达式;引用外部超类
;分隔同一行的多个语句
$在字符串模板中引用变量或表达式
_替代 lambda 表达式中未使用的参数;替代解构声明中未使用的参数

Packages and imports

Packages and imports

源文件可以以包声明开头:

package org.example

fun printMessage() { /*...*/ }
class Message { /*...*/ }

// ...

源文件中的所有内容(例如类和函数)均包含在该包中。因此,在上述示例中:

  • 函数 printMessage() 的全名为 org.example.printMessage
  • 类 Message 的全名为 org.example.Message

若未指定包声明,则该文件的所有内容属于无名称的默认包。

Default imports

Kotlin 会为每个文件 Default imports 多个包,无需手动声明:

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

平台特定的额外导入

根据目标平台不同,会额外导入以下包:

JVM 平台:

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

JS 平台:

  • kotlin.js.*

Imports

除默认导入外,每个文件还可包含自定义的导入指令。

1. 导入单个名称

import org.example.Message // 无需限定名即可直接使用 Message

2. 导入某个作用域的所有可访问内容

(支持包、类、对象等作用域)

import org.example.* // 'org.example' 包下的所有内容均可直接使用

3. 名称冲突解决

若导入的内容存在名称冲突,可使用 as 关键字局部重命名冲突实体:

import org.example.Message // 直接使用 Message(对应 org.example.Message)
import org.test.Message as TestMessage // 用 TestMessage 指代 org.test.Message

4. 导入范围扩展

import 关键字并非仅用于导入类,还支持导入其他声明:

  • 顶层函数与属性
  • 对象声明中定义的函数与属性
  • 枚举常量

顶层声明的可见性

若顶层声明被标记为 private,则其仅在声明所在的文件内可见(详见「可见性修饰符」)。

Annotations

注解是向代码附加元数据的一种方式。声明注解时,需在类前添加 annotation 修饰符:

annotation class Fancy

注解的额外属性可通过元注解(标注在注解类上的注解)指定:

  • @Target:指定该注解可标注的元素类型(如类、函数、属性、表达式等);
  • @Retention:指定注解是否存储在编译后的类文件中,以及是否可在运行时通过反射访问(默认均为 true);
  • @Repeatable:允许在单个元素上多次使用同一注解;
  • @MustBeDocumented:指定该注解属于公共 API,需包含在生成的 API 文档的类或方法签名中。
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
        AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.VALUE_PARAMETER,
        AnnotationTarget.EXPRESSION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
annotation class Fancy

Usage

@Fancy class Foo {
    @Fancy fun baz(@Fancy foo: Int): Int {
        return (@Fancy 1) // 标注表达式
    }
}
// 1. 标注在类上:整个 Foo 类被 @Fancy 注解标记
@Fancy class Foo {
    // 2. 标注在函数上:baz() 函数被 @Fancy 注解标记
    @Fancy fun baz(
        // 3. 标注在函数参数上:foo 参数被 @Fancy 注解标记
        @Fancy foo: Int
    ): Int {
        // 4. 标注在表达式上:数值 1 这个表达式被 @Fancy 注解标记
        return (@Fancy 1) // 标注表达式
    }
}

如果把 @Fancy 换成实际有功能的注解(比如 @Deprecated 或自定义的日志注解),就更容易理解:

@Deprecated("Foo 类已过时") // 标注类
class Foo {
    @Deprecated("baz 函数已过时") // 标注函数
    fun baz(
        @NotNull foo: Int // 标注参数(非空校验)
    ): Int {
        return (@CheckReturnValue 1) // 标注返回值表达式(校验返回值是否被使用)
    }
}

若需标注类的主构造函数,需显式添加 constructor 关键字,并在其前添加注解:

class Foo @Inject constructor(dependency: MyDependency) { ... }

也可标注属性的访问器(getter/setter):

class Foo {
    var x: MyDependency? = null
        @Inject set // 标注 setter 方法
}
构造函数(Constructors)

注解可包含带参数的构造函数:

annotation class Special(val why: String)

@Special("example") class Foo {}

允许的参数类型:

  1. 与 Java 基本类型对应的类型(Int、Long 等);
  2. 字符串(String);
  3. 类(Foo::class);
  4. 枚举(enum);
  5. 其他注解;
  6. 上述类型的数组。

注意:注解参数不能是可空类型,因为 JVM 不支持将 null 存储为注解属性值。

若注解作为其他注解的参数,其名称无需添加 @ 前缀:

annotation class ReplaceWith(val expression: String)

annotation class Deprecated(
        val message: String,
        val replaceWith: ReplaceWith = ReplaceWith("") // 直接使用注解类名
)

@Deprecated("This function is deprecated, use === instead", ReplaceWith("this === other"))
fun oldFunction(other: Any): Boolean = this == other

若需将类指定为注解参数,需使用 Kotlin 类(KClass)——Kotlin 编译器会自动将其转换为 Java 类,确保 Java 代码可正常访问该注解及参数:

import kotlin.reflect.KClass

annotation class Ann(val arg1: KClass<*>, val arg2: KClass<out Any>)

@Ann(String::class, Int::class) class MyClass

实例化(Instantiation)

在 Java 中,注解类型是接口的一种形式,可实现并创建实例;Kotlin 则允许在任意代码中调用注解类的构造函数,直接创建实例:

annotation class InfoMarker(val info: String)

fun processInfo(marker: InfoMarker): Unit = TODO()

fun main(args: Array<String>) {
    if (args.isNotEmpty())
        processInfo(getAnnotationReflective(args)) // 反射获取注解实例
    else
        processInfo(InfoMarker("default")) // 直接实例化注解
}

Lambda 表达式(Lambdas)

注解也可用于 Lambda 表达式,此时注解会应用于 Lambda 体生成的 invoke() 方法。该特性适用于 Quasar 等框架(通过注解实现并发控制):

annotation class Suspendable

val f = @Suspendable { Fiber.sleep(10) } // 注解 Lambda 表达式

注解使用位置目标(Annotation use-site targets)

当标注属性或主构造函数参数时,对应的 Kotlin 元素会生成多个 Java 元素(如字段、getter、构造函数参数),因此注解在生成的 Java 字节码中有多个可能的位置。需通过特定语法指定注解的生成位置:

class Example(
    @field:Ann val foo,    // 仅标注 Java 字段
    @get:Ann val bar,      // 仅标注 Java getter 方法
    @param:Ann val quux    // 仅标注 Java 构造函数参数
)

该语法也可用于标注整个文件:需将带 file 目标的注解放在文件顶层(包声明之前,或默认包下的所有导入之前):

@file:JvmName("Foo") // 标注文件,指定生成的 Java 类名为 "Foo"

package org.jetbrains.demo

若多个注解有相同的使用位置目标,可将目标后加方括号,所有注解放入括号中(all 元目标除外),避免重复目标声明:

class Example {
     @set:[Inject VisibleForTesting] // 同时标注 setter 方法
     var collaborator: Collaborator
}

支持的使用位置目标完整列表:

目标名称说明
file标注整个文件
field标注 Java 字段
property标注 Kotlin 属性(该目标的注解对 Java 不可见)
get标注属性的 getter 方法
set标注属性的 setter 方法
all实验性元目标(用于属性,详见下文说明)
receiver标注扩展函数 / 属性的接收者参数
param标注构造函数参数
setparam标注属性 setter 的参数
delegate标注委托属性中存储委托实例的字段

标注扩展函数的接收者参数:

fun @receiver:Fancy String.myExtension() { ... } // 标注 String 类型的接收者

未指定使用位置目标时的默认行为

若未指定使用位置目标,目标会根据被使用注解的 @Target 自动选择。若有多个适用目标,按以下顺序选择第一个适用目标:

  1. param(构造函数参数)
  2. property(Kotlin 属性)
  3. field(Java 字段)

以 Jakarta Bean Validation 的 @Email 注解为例:

// Java 中的 @Email 注解
@Target(value={METHOD,FIELD,ANNOTATION_TYPE,CONSTRUCTOR,PARAMETER,TYPE_USE})
public @interface Email { }

应用于 Kotlin 代码:

data class User(
    val username: String,
    @Email val email: String, // 等价于 @param:Email(未指定目标时,优先选择 param)
    @Email val secondaryEmail: String? = null // 等价于 @field:Email(属性字段,选择 field)
)

Kotlin 2.2.0 引入了实验性默认规则,使注解向参数、字段、属性的传播更可预测。新规则下,若有多个适用目标,选择逻辑如下:

  1. 若 param(构造函数参数)适用,则使用该目标;
  2. 若 property(Kotlin 属性)适用,则使用该目标;
  3. 若 field(Java 字段)适用且 property 不适用,则使用 field。

沿用上述示例,新规则下的行为:

data class User(
    val username: String,
    @Email val email: String, // 等价于 @param:Email + @field:Email(同时标注两个目标)
    @Email val secondaryEmail: String? = null // 仍等价于 @field:Email
)

若多个目标均不包含 param、property、field,则注解无效。

启用新默认规则:需在 Gradle 配置中添加以下编译参数:

// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xannotation-default-target=param-property")
    }
}

恢复旧行为的方式:

  1. 特定场景:显式指定目标(如用 @param:Annotation 替代 @Annotation);
  2. 整个项目:在 Gradle 中添加以下编译参数:
// build.gradle.kts
kotlin {
    compilerOptions {
        freeCompilerArgs.add("-Xannotation-default-target=first-only")
    }
}

说明:上述新默认规则为实验性特性,未来可能调整。

Visibility modifiers

可见性修饰符

类、对象、接口、构造函数、函数,以及属性及其 setter 方法均可拥有可见性修饰符。Getter 方法的可见性始终与其所属属性相同。

Kotlin 中有四种可见性修饰符:private、protected、internal 和 public。默认可见性为 public。

Packages

函数、属性、类、对象和接口可直接声明在包内的 “顶层”:

// 文件名称:example.kt
package foo

fun baz() { ... }
class Bar { ... }
  • 若未使用可见性修饰符,默认使用 public,表示声明在任何地方均可见。
  • 若标记为 private,则仅在包含该声明的文件内可见。
  • 若标记为 internal,则在同一模块内的任何地方可见。//即一个gradle文件的范围
  • protected 修饰符不适用于顶层声明。

要从其他包使用可见的顶层声明,需导入该声明。

// 文件名称:example.kt
package foo

private fun foo() { ... } // 仅在 example.kt 内可见

public var bar: Int = 5 // 属性在任何地方可见
    private set         // setter 仅在 example.kt 内可见

internal val baz = 6    // 同一模块内可见

Class members

对于类内部声明的成员:

  • private:成员仅在当前类内部可见(包括其所有成员)。
  • protected:成员拥有与 private 相同的可见性,且在子类中也可见。
  • internal:同一模块内任何能看到声明类的客户端,均可看到其 internal 成员。
  • public:任何能看到声明类的客户端,均可看到其 public 成员。

注意:在 Kotlin 中,外部类无法访问其内部类的 private 成员。

若重写 protected 或 internal 成员且未显式指定可见性,重写后的成员将继承原成员的可见性。

示例:

kotlin

open class Outer {
    private val a = 1
    protected open val b = 2
    internal open val c = 3
    val d = 4  // 默认 public

    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a 不可见
    // b、c、d 可见
    // Nested 类及 e 可见

    override val b = 5   // 'b' 保持 protected
    override val c = 7   // 'c' 保持 internal
}

class Unrelated(o: Outer) {
    // o.a、o.b 不可见
    // o.c、o.d 可见(同一模块)
    // Outer.Nested 不可见,Nested::e 也不可见
}

构造函数(Constructors)

使用以下语法指定类主构造函数的可见性:

需显式添加 constructor 关键字。

kotlin

class C private constructor(a: Int) { ... }

此处构造函数为 private。默认情况下,所有构造函数均为 public,即类可见的地方构造函数均可见(这意味着 internal 类的构造函数仅在同一模块内可见)。

对于密封类(sealed class),构造函数默认是 protected。更多信息参见「密封类(Sealed classes)」。


局部声明(Local declarations)

局部变量、函数和类不能拥有可见性修饰符。


模块(Modules)

internal 可见性修饰符表示成员在同一模块内可见。具体来说,模块是一组一起编译的 Kotlin 文件,例如:

  • IntelliJ IDEA 模块。
  • Maven 项目。
  • Gradle 源集(例外:test 源集可访问 main 源集的 internal 声明)。
  • 通过一次 <kotlinc> Ant 任务编译的文件集合。

总结

  • Kotlin 的可见性修饰符分为 private(文件 / 类内)、protected(类 + 子类)、internal(模块内)、public(全局),默认是 public。
  • 顶层声明不支持 protected,局部声明无可见性修饰符。
  • internal 是 Kotlin 特有修饰符,以 “模块” 为边界控制可见性,适合多模块项目协作(如车载开发中的模块化架构)。
Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐