Kotlin 修饰符整理
一、可见性修饰符
Kotlin
的可见性修饰符用于控制代码中类、对象、接口、构造函数、函数、属性及其setter
的访问权限。Kotlin
提供四种修饰符:public、internal、protected 和 private。
1. public
(默认)
-
作用范围:所有地方可见。
-
说明:
- 若不显式指定修饰符,默认即为
public
。 - 顶层声明(如函数、类)可在任何位置访问。
- 类成员(如方法、属性)在类可见的前提下,可被任意访问。
示例:
class PublicExample { val publicProperty = "Accessible everywhere" }
- 若不显式指定修饰符,默认即为
2. internal
-
作用范围:同一模块(Module)内可见。
-
说明:
- 模块指一起编译的一组 Kotlin 文件(如 Gradle 模块、Maven 项目)。
- 用于隐藏模块内部实现,对外暴露有限接口。
示例:
internal class InternalExample { internal val internalProperty = "Only within the same module" }
3. protected
-
作用范围:类内部及其子类中可见。
-
说明:
- 仅适用于类成员(不适用于顶层声明)。
- 子类可以访问父类的
protected
成员,但同一包内无继承关系的类无法访问。
示例:
open class Base { protected val protectedProperty = "Accessible in subclass" } class Derived : Base() { fun printProperty() { println(protectedProperty) // 子类可访问 } }
4. private
- 作用范围:
- 顶层声明:仅在定义的文件内可见。
- 类成员:仅在定义的类或伴生对象内可见。
- 说明:最严格的可见性,用于彻底封装实现细节。
示例:private class PrivateClass // 仅在此文件内可见 class MyClass { private val privateProperty = "Only inside MyClass" companion object { private fun privateMethod() {} // 伴生对象内可见 } }
其他规则与注意事项
构造函数可见性
- 主构造函数的可见性通过
constructor
关键字显式指定:class MyClass private constructor(val param: Int) // 私有构造函数
Getter
的可见性
- 属性的
Getter
始终与属性可见性一致。 Setter
可单独设置更严格的可见性:var mutableProperty: String = "" private set // 外部不可修改
覆盖成员的可见性
- 子类中覆盖的成员可见性不能低于父类(如父类为
protected
,子类可为public
)。 - 禁止缩小可见性(如父类为
public
,子类不能设为protected
)。
对比 Java
Kotlin | Java | 区别 |
---|---|---|
public | public | Kotlin 默认即为 public 。 |
internal | 无直接等价 | Java 无模块级可见性。 |
protected | protected | Kotlin 仅允许子类访问,同一包内不可。 |
private | private | Kotlin 的 private 支持文件级封装。 |
二、类及其成员的修饰符
- 在
Kotlin
中,类及其成员的修饰符不仅包括可见性修饰符(如 public、private 等),还包含其他用于定义类特性或成员行为的修饰符。
1. open
:允许类被继承或方法被重写
- Kotlin 默认所有类都是
final
(不可继承)。若需允许继承,需显式标记open
。
示例:open class Animal { /* 允许被继承 */ } class Dog : Animal() { /* 继承 */ } open class Base { //允许方法被重写 open fun print() { println("Base") } } class Child : Base() { //覆盖重写 override fun print() { println("Child") } }
2. override
:覆盖成员
- 显式标记覆盖父类或接口中的成员。
示例:open class Base { open fun print() { println("Base") } } class Child : Base() { override fun print() { println("Child") } }
3. abstract
:抽象类
- 抽象类不能直接实例化,需由子类实现其抽象成员。
- 抽象类中可以有抽象方法和具体实现。
示例:abstract class Shape { abstract fun draw() // 抽象方法,需子类实现 fun rotate() { println("Rotating") } // 具体实现 }
4. data
:数据类
- 自动生成
equals()
、hashCode()
、toString()
、copy()
、componentN()
方法。 - 要求:主构造函数至少有一个参数,且所有参数需标记为
val
或var
。
示例:data class User(val name: String, val age: Int)
5. sealed
:密封类
- 限制类的继承结构,子类需在密封类声明文件中完成,扩展性受限。
- 所有子类通常需在 同一文件中定义,可能导致文件过大(可通过模块化解决)。
- 本质是抽象类,不可直接实例化,且构造函数默认私有(子类需显式调用父类构造函数)
- 子类的可见性不能高于密封类(如密封类为
internal
,子类不能为public
)。
示例:sealed class Result { data class Success(val data: String) : Result() data class Error(val message: String) : Result() object Loading : Result() } fun handleResult(result: Result) = when (result) { is Result.Success -> println(result.data) is Result.Error -> println(result.message) Result.Loading -> println("Loading...") // 无需 else 分支(编译器检查所有情况) }
6. object
:对象类
对象声明(Object Declaration)
- 用途: 直接声明一个单例对象(全局唯一实例),替代 Java 静态成员或单例模式。
- 特性:
- 线程安全,首次访问时初始化(懒加载)。
- 可继承类和实现接口。
object DatabaseManager {
init {
println("单例初始化")
}
fun connect() {
println("连接数据库")
}
}
// 使用
DatabaseManager.connect() // 直接通过类名调用
//可继承类和实现接口
object ConfigLoader : FileLoader(), Serializable { ... }
对象表达式(Object Expression)
- 用途: 创建匿名对象(类似 Java 匿名内部类),常用于事件监听或临时对象。
- 特性:
- 可访问外部作用域的变量(非
final
变量也能修改)。 - 匿名对象的类型是生成的超类型子类,无法显式引用。
示例:
val listener = object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { println("点击事件") } fun customMethod() { ... } } // 调用 listener.mouseClicked(event)
- 可访问外部作用域的变量(非
7. companion
:伴生对象 (Companion Object)
- 用途: 为类定义静态成员(替代 Java
static
关键字),同时支持接口实现和扩展函数。
语法与示例:class User { companion object { const val DEFAULT_NAME = "Guest" fun create(name: String): User { return User(name) } } // 其他成员 } // 调用 User.DEFAULT_NAME // 类似静态属性 User.create("Alice") // 类似静态方法
- 高级用法:
- 实现接口:
class MyClass { companion object : JsonSerializer<MyClass> { ... } }
- 扩展函数:
fun User.Companion.fromJson(json: String): User { ... }
8. Enum
:枚举类
- 使用
enum class
声明,每个枚举常量是其实例 - 枚举常量是单例对象,类型为枚举类本身(如
Direction.NORTH
的类型是Direction
)。 - 默认提供
values()
和valueOf()
方法,用于遍历和查找枚举值。enum class Direction { NORTH, SOUTH, EAST, WEST }
- 带属性的枚举
每个枚举常量可携带自定义数据:enum class Color(val rgb: Int) { RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF) } // 使用 val redHex = Color.RED.rgb // 输出: 16711680
- 实现接口或重写方法
枚举常量可单独实现抽象方法或覆盖通用行为:enum class Operation { ADD { override fun apply(a: Int, b: Int) = a + b }, SUBTRACT { override fun apply(a: Int, b: Int) = a - b }; abstract fun apply(a: Int, b: Int): Int }
9. inner
:内部类
- 持有外部类引用
内部类默认持有外部类的实例引用,可直接访问外部类的成员(包括私有成员)。
class Outer {
private val outerProperty = "Outer Property"
inner class Inner {
fun printInfo() {
println(outerProperty) // ✅ 直接访问外部类属性
}
fun accessOuter() = println(outerProp) // 可访问外部类成员
}
}
- 实例化方式
内部类必须通过外部类实例创建:val outer = Outer() val inner = outer.Inner() // 通过外部类实例调用
10. lateinit
:延迟初始化
- 用于非空属性的延迟初始化(避免
var
的可空性)。 - 限制:只能用于
var
,且不能用于基本类型(如Int
、Boolean
)。
示例:class MyClass { lateinit var name: String fun init() { name = "Kotlin" } }
11. const
:编译时常量
- 标记为编译时常量,必须为基本类型或
String
,且定义在顶层或companion object
中。
示例:const val PI = 3.14 // 顶层常量 class Constants { companion object { const val MAX = 100 } }
12. operator
:运算符重载
- 重载运算符
(如 +、==、get、set),表达式a + b
会被编译为a.plus(b)
。
示例:data class Point(val x: Int, val y: Int) { operator fun plus(other: Point) = Point(x + other.x, y + other.y) } // 使用:val p3 = p1 + p2
- 扩展函数重载
运算符可定义为扩展函数,增强现有类的功能:operator fun Point.minus(other: Point): Point { return Point(x - other.x, y - other.y) }
13. infix
:中缀函数
infix
是 Kotlin 的关键字,用于定义中缀函数,允许以更简洁的语法 (如 A 函数名 B) 调用函数,替代传统的点符号 (A.函数名(B))。
自定义中缀函数class Person(val name: String) { infix fun knows(other: Person): Boolean = name == other.name } fun main() { val alice = Person("Alice") val bob = Person("Bob") println(alice knows bob) // 输出: false } infix fun String.eq(value: Any) = "$this = $value" val query = select from "users" where ("age" eq 25)
对比 Java
Kotlin 修饰符 | Java 等价或类似 | 关键差异 |
---|---|---|
open | 无 final 修饰的类 | Kotlin 默认类为 final ,Java 默认非 final |
data | Lombok 的 @Data | Kotlin 直接生成标准方法 |
sealed | 无直接等价 | Java 需手动限制子类 |
lateinit | 无直接等价 | 避免基本类型可空性 |
companion object | static 成员 | Kotlin 通过伴生对象实现静态成员 |
三、函数修饰符
1. inline
:内联函数
- 编译期替换
调用inline
函数时,编译器会将函数体代码直接复制到调用位置,而非生成函数调用指令。inline fun log(message: () -> String) { println(message()) } // 调用处代码会被替换为:println("Debug") log { "Debug" }
- 禁用场景
- 函数参数被存储(如赋值给变量)
- 递归函数
// ❌❌❌错误示例:Lambda 被存储 inline fun saveLambda(block: () -> Unit) { val saved = block // 编译报错❌❌❌ }
2. noinline
:控制内联范围
- 必须与
inline
搭配,使用noinline
标记不需要内联的 Lambda 参数:inline fun fetchData(noinline onSuccess: () -> Unit) { ... }
3. crossinline
:允许内联但限制非局部返回
- 必须与
inline
搭配,crossinline
仅能修饰inline
函数的 Lambda 参数。 - 局部返回仍可用,Lambda 内可通过
return@label
显式指定返回点。 - 性能无额外开销,与普通
inline
函数相同,仅增加编译期检查。inline fun example( noinline skip: () -> Unit, // 不内联,可作为对象存储 crossinline validate: () -> Unit // 内联但禁止非局部返回 ) { val stored = skip // 合法 Thread { validate() }.start() // 合法,但 validate 不能直接 `return` }
4. tailrec
:尾递归优化
- 尾递归是递归的一种特殊形式,要求函数在调用自身后不进行任何额外操作(即递归调用必须是函数的最后一步操作)。Kotlin 通过
tailrec
关键字在编译时将尾递归转换为迭代,避免栈溢出并提升性能。 - 递归调用必须在函数末尾
函数最后一行必须是递归调用自身,且无后续操作。// 正确示例 tailrec fun factorial(n: Int, acc: Int = 1): Int { if (n <= 1) return acc return factorial(n - 1, n * acc) // 无额外操作 }
- 仅支持直接递归调用
间接递归(如 A 调用 B,B 再调用 A)无法优化。 - 编译期转换
tailrec
会将递归代码转换为等效的while
循环,消除栈帧累积。// 编译后等效代码(伪代码) fun factorial(n: Int): Int { var acc = 1 var current = n while (current > 1) { acc *= current current-- } return acc }
- 数据结构遍历
链表、树结构的搜索(需满足尾递归条件)。tailrec fun findNode(head: ListNode?, target: Int): ListNode? { head ?: return null if (head.value == target) return head return findNode(head.next, target) }
- 禁止在递归调用后执行运算
以下情况无法优化:// v错误示例:递归结果参与乘法运算 tailrec fun badFactorial(n: Int): Int { if (n == 1) return 1 return n * badFactorial(n - 1) // 编译报错❌❌❌ }
5. vararg:可变数量参数
vararg
(Variable Arguments)允许函数接受任意数量的同类型参数,在 Kotlin 中通过vararg
关键字实现,类似于 Java 的 … 语法。- 基础使用
fun printAll(vararg messages: String) { for (msg in messages) println(msg) } fun main() { printAll("Hello", "Kotlin", "World") // 直接传递多个参数 }
- 结合数组传递
fun mergeArrays(vararg arrays: IntArray): IntArray { return arrays.flatMap { it.toList() }.toIntArray() } fun main() { val arr1 = intArrayOf(1, 2) val arr2 = intArrayOf(3, 4) val merged = mergeArrays(arr1, arr2) // 无需展开 }
- 与其他参数混合
fun formatLog(tag: String, vararg entries: String): String { return "$tag: ${entries.joinToString()}" } fun main() { println(formatLog("DEBUG", "Error", "Warning")) // 输出: DEBUG: Error, Warning }
四、初始化与访问控制
1. init
:主构造函数逻辑扩展
init
是 Kotlin 中用于类初始化逻辑的特殊代码块,在对象创建时自动执行。- 执行时机:在类实例化时,init 块和属性初始化器按代码顺序执行依次执行(早于次构造函数)
- 可访问主构造函数参数:可直接使用主构造函数的参数(如校验参数合法性)
- 允许多个
init
块:按声明顺序执行,通常用于分阶段初始化 - 当主构造函数无法直接包含复杂逻辑时(如参数校验、属性联动计算),
init
可补充初始化代码。class User(name: String) { val firstName: String init { firstName = name.split(" ")[0] // 复杂初始化 } }
- 参数校验与预处理
class Database(url: String) { init { require(url.isNotBlank()) { "URL不能为空" } // 快速失败校验 } }
- 复杂属性初始化
class Config { val properties: Map<String, String> init { properties = loadConfigFile() // 耗时操作 } }
- 依赖主构造函数参数的逻辑
class Person(name: String) { val nameLength: Int init { nameLength = name.length // 基于参数计算 } }
2. field
:属性幕后字段(用于自定义 getter/setter)
- 幕后字段定义
在 Kotlin 中,每个属性默认生成一个隐藏的存储字段(即field
),用于保存属性的实际值。var name: String = "Kotlin" // 编译后自动生成幕后字段存储 "Kotlin"
- 自定义访问器逻辑
在get()
或set()
中访问或修改原始值时需通过field
var age: Int = 0 set(value) { field = value.coerceAtLeast(0) }
- 避免递归调用
直接使用属性名会触发访问器,导致无限递归;field
可绕过访问器直接操作值var count: Int = 0 set(value) { if (value > 0) field = value }
- 注意事项
- 非必须使用的情况:若属性值通过计算获得(如派生属性),则无需
field
:val area: Double get() = width * height // 无幕后字段
- 访问限制:
field
仅在属性访问器( get() / set() )内部可用,外部直接访问属性会调用访问器。 - 与后备属性的区别: 后备属性是显式定义的私有属性,而幕后字段是编译器自动生成的隐式存储。
class User { // 示例1:通过 field 校验赋值 var email: String = "" set(value) { field = if (value.contains("@")) value else throw IllegalArgumentException("Invalid email") } // 示例2:无幕后字段的计算属性 val fullName: String get() = "$firstName $lastName" }
- 非必须使用的情况:若属性值通过计算获得(如派生属性),则无需
3. by
: 委托
- 类委托(Class Delegation)
- 通过
by
将接口的实现委托给另一个对象,避免直接继承:interface Animal { fun makeSound() } class Dog : Animal { override fun makeSound() = println("Woof!") } // 将 Animal 接口的实现委托给 dog 对象 class Robot(private val dog: Dog) : Animal by dog { fun move() = println("Robot moving") } fun main() { val robot = Robot(Dog()) robot.makeSound() // 实际调用 Dog 的 makeSound() robot.move() // Robot 的独有方法 }
- 优势:
- 避免继承带来的脆弱性,优先组合而非继承。
- 复用现有类的功能,保持代码简洁。
- 通过
- 属性委托(Property Delegation)
将属性的getter
/setter
逻辑委托给其他对象:- 标准委托(如 by lazy)
val lazyValue by lazy { println("Initialized once") "Lazy Result" } fun main() { println(lazyValue) // 首次访问时初始化 println(lazyValue) // 直接返回缓存值 }
- 特性:
- 线程安全(默认同步锁)。
- 延迟初始化,首次访问时计算并缓存结果。
- 标准委托(如 by lazy)
- 自定义委托
需实现getValue()
和setValue()
方法:class RangeValidator(private val min: Int, private val max: Int) { operator fun getValue(thisRef: Any?, property: KProperty<*>): Int { return field // 实际值存储在幕后字段 } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) { field = if (value in min..max) value else throw IllegalArgumentException("超出范围") } } var score: Int by RangeValidator(0, 100) // 委托给 RangeValidator
五、协程与并发修饰符
关键字 | 核心作用 | 典型应用示例 |
---|---|---|
suspend | 标记挂起函数(协程上下文调用) | 异步任务或耗时操作 协程集成:suspend 函数需在协程作用域或另一个 suspend 函数中调用。 |
volatile | 保证变量可见性(多线程环境) | 共享变量的线程安全访问 |
synchronized | 方法级别同步锁(Java 互操作性) | 临界区资源保护 |
六、其他修饰符
关键字 | 核心作用 | 典型应用示例 |
---|---|---|
external | 声明外部实现(如 JNI 或 JavaScript) | 调用本地代码或跨平台接口 |
annotation | 定义注解类 | 元数据处理或框架扩展 |
companion | 声明伴生对象(类似 Java 静态成员) | 工厂方法或类级常量定义 |
infix | 允许中缀函数调用(如 a to b) | 增强 DSL 可读性 |
expect/actual | 多平台声明与实现(跨平台项目) | 定义公共接口与平台特定实现 |