Kotlin ‌修饰符整理

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

KotlinJava区别
publicpublicKotlin 默认即为 public
internal无直接等价Java 无模块级可见性。
protectedprotectedKotlin 仅允许子类访问,同一包内不可。
privateprivateKotlin 的 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() 方法。
  • ‌要求‌:主构造函数至少有一个参数,且所有参数需标记为 valvar
    ‌示例‌:
    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")    // 类似静态方法
    
  • 高级用法‌:
    1. ‌实现接口‌:
    class MyClass {
        companion object : JsonSerializer<MyClass> { ... }
    }
    
    1. 扩展函数‌:
    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,且不能用于基本类型(如 IntBoolean)。
    示例‌:‌
    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 等价或类似关键差异
openfinal 修饰的类Kotlin 默认类为 final,Java 默认非 final
dataLombok 的 @DataKotlin 直接生成标准方法
sealed无直接等价Java 需手动限制子类
lateinit无直接等价避免基本类型可空性
companion objectstatic 成员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 
    }
    
  • 注意事项
    1. 非必须使用的情况‌:若属性值通过计算获得(如派生属性),则无需 field
      val area: Double  
          get() = width * height  // 无幕后字段
      
    2. 访问限制‌: field 仅在属性访问器( get() / set() )内部可用,外部直接访问属性会调用访问器。
    3. 与后备属性的区别‌: 后备属性是显式定义的私有属性,而幕后字段是编译器自动生成的隐式存储。
    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)  // 直接返回缓存值
      }
      
    • 特性
      • 线程安全(默认同步锁)。
      • 延迟初始化,首次访问时计算并缓存结果。
  • 自定义委托‌
    需实现 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多平台声明与实现(跨平台项目)定义公共接口与平台特定实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值