Kotlin 特殊函数

一、语法修饰符修饰的特殊函数(语言特性,Java不存在)

1. 扩展函数 / 扩展属性 fun T.xxx()

定义

不修改原有类源码,为类新增函数;本质是静态工具方法,第一个参数为目标对象。

fun String.isValidPhone(): Boolean {
    // this = 调用者对象
    return this.matches(Regex("^1[3-9]\\d{9}$"))
}
// 调用
"13800000000".isValidPhone()

底层字节码等效Java

public static boolean isValidPhone(String $this) {}

⚠️核心陷阱(面试高频)

  1. 静态分发:扩展函数无法重写。变量声明类型决定调用哪个扩展函数,不是实际对象类型
open class Animal
class Cat : Animal()

fun Animal.say() = "animal"
fun Cat.say() = "cat"

val a: Animal = Cat()
println(a.say()) // 输出 animal!不会动态分派
  1. 若成员函数与扩展函数同名、同参数,成员函数优先级永远更高,扩展函数被隐藏。
  2. 扩展函数不能访问类 private/protected 成员。
  3. 可定义可空类型扩展函数 fun String?.test(){},安全调用空对象。

2. infix 中缀函数

使用条件(全部满足)

  1. 成员函数 / 扩展函数
  2. 仅有一个参数
  3. 函数标记 infix

语法:省略 . 和 ()

infix fun Int.pow(n: Int): Int {
    var res = 1
    repeat(n) { res *= this }
    return res
}
fun main(){
    val num = 2 pow 3 // 等价 2.pow(3)
}

标准库经典:to 创建 Pair

val map = mapOf(1 to "A") // infix fun <A,B> A.to(that:B):Pair<A,B>

限制

不能支持具名参数、可变参数;参数不能带默认值。

3. tailrec 尾递归函数

普通递归问题

每一次递归调用压入栈帧,深度过大 → StackOverflowError

尾递归要求:

递归调用必须是函数最后一条有效执行语句(尾部调用),不能参与运算
✅正确:return fact(n-1, n*acc)
❌错误:return n * fact(n-1)(递归结果还要参与乘法,不是尾调用)

tailrec fun factorial(n: Long, acc: Long = 1): Long {
    if(n <= 1) return acc
    return factorial(n - 1, n * acc) // 尾部调用
}

底层

编译器直接将递归优化为while循环,不创建栈帧。

⚠️ 如果不满足尾递归条件,编译器不会报错,仅仅不会优化。

4. inline / noinline / crossinline 内联函数

4.1 inline

普通高阶函数:传递Lambda会生成匿名内部类,频繁调用产生对象开销。
inline:编译期把函数体 + Lambda代码直接拷贝到调用处,消除函数对象创建。

inline fun measureTime(block: ()->Unit): Long{
    val start = System.currentTimeMillis()
    block()
    return System.currentTimeMillis() - start
}

关键特性:非局部返回

内联Lambda里可以直接 return,跳出外层函数(普通Lambda只能return@标签)

inline fun test(block: ()->Unit){ block() }
fun demo(){
    test {
        return // 直接退出demo(),非局部返回
    }
}

4.2 noinline

一个inline函数接收多个lambda,只想部分Lambda不内联,标记 noinline。
适用场景:需要把lambda作为参数继续传递、存入变量(内联代码没有对象,无法传递)

inline fun run(a:()->Unit, noinline b:()->Unit){
    a()
    val func: ()->Unit = b // noinline lambda可以赋值给变量
}

4.3 crossinline

禁止Lambda中非局部return,强制只能带标签返回。
场景:Lambda不在当前函数直接执行(交给另一个线程、异步执行),非局部return会造成逻辑异常。

inline fun asyncRun(crossinline block: ()->Unit) {
    Thread {
        block()
    }.start()
}
// 调用处
asyncRun {
    // return // 编译报错!禁止非局部返回
    return@asyncRun // 只能标签返回
}

5. suspend 挂起函数(协程核心特殊函数)

suspend fun requestData(): String {
    delay(1000) // 挂起函数,不阻塞线程
    return "result"
}

硬性规则

  1. 只能在 suspend函数 / 协程作用域内调用,普通函数无法直接调用;
  2. 本质:编译器自动增加隐藏参数 Continuation<T>,没有任何线程魔法;
  3. 挂起 ≠ 阻塞:线程释放,可以去执行其他任务;恢复时不一定在原线程。

陷阱

suspend 仅仅是调用限制标记,不会自动切换线程;切换线程依靠 withContext。

二、运算符重载函数(operator 特殊函数)

必须加 operator 关键字,固定函数名,允许使用运算符语法调用。

函数签名 运算符 说明
operator fun plus(other:T) + 加
operator fun minus(other:T) - 减
operator fun times(other:T) * 乘
operator fun div(other:T) / 除
operator fun rem(other:T) % 取余
operator fun unaryMinus() -obj 负号一元运算
operator fun unaryPlus() +obj 正号一元运算
operator fun not() !obj 取反
operator fun get(i:Int) obj[i] 下标读取
operator fun set(i:Int,v:T) obj[i]=v 下标赋值
operator fun invoke() obj() 对象当作函数调用
operator fun contains(other:T) x in obj in 判断
operator fun compareTo(other:T) > < >= <= 大小比较

invoke 示例(超级常用)

class Logger {
    operator fun invoke(msg: String) {
        println("log:$msg")
    }
}
val log = Logger()
log("测试消息") //等价 log.invoke("测试消息")

面试经常问:invoke 使用场景:函数式对象、封装请求、点击代理。

三、标准库五大作用域函数(let/run/apply/also/with)【重中之重】

底层全部基于带接收者的函数字面量 T.() -> R

函数 接收者引用 返回值 函数签名 核心场景
T.let(block: (T)->R) it(参数) lambda返回结果 扩展函数 空安全 ?.let{},对象转换
T.run(block:T.()->R) this lambda返回结果 扩展函数 计算逻辑、临时作用域
T.apply(block:T.()->Unit) this 自身this 扩展函数 对象初始化、配置属性
T.also(block:(T)->Unit) it 自身this 扩展函数 日志打印、附加操作、不破坏链式调用
with(obj:T, block:T.()->R) this lambda返回结果 普通函数(非扩展) 大量连续调用同一对象方法

完整示例对比

data class User(var name: String, var age: Int)

fun main(){
    val origin = User("张三",18)

    // apply:配置对象,返回自身
    val user1 = origin.apply { age = 20 }

    // also:附加操作,返回自身
    val user2 = origin.also { println(it.name) }

    // let:转换对象,返回lambda结果
    val userName:String = origin.let { it.name }

    // run:作用域计算
    val info = origin.run { "$name,$age" }

    // with
    val desc = with(origin){ "$name | $age" }
}

选型口诀

  1. 初始化构建对象 → apply
  2. 额外打印/埋点,不想打断链式 → also
  3. 空安全 + 对象转换 → let
  4. 无需对象、仅需要局部作用域 → 独立 run{}
  5. 大量调用同一个对象,不想重复写变量名 → with

配套衍生函数

takeIf / takeUnless

// takeIf:条件true返回自身,false返回null
val str:String? = "hello".takeIf { it.isNotEmpty() }
// takeUnless:条件false返回自身,true返回null
val str2:String? = "".takeUnless { it.isEmpty() }

典型搭配:obj.takeIf{}.let{}

四、Data Class 编译器自动生成特殊函数

定义 data class,编译器自动生成5套函数

data class Book(val id:Int, val name:String)
  1. component1()、component2()... 解构声明使用
val (id,name) = Book(1,"Kotlin")
// 等价
val id = book.component1()
val name = book.component2()
  1. equals():按所有属性值比较
  2. hashCode():基于所有属性生成哈希
  3. toString():格式化输出所有属性
  4. copy():复制对象,选择性修改属性
val b1 = Book(1,"A")
val b2 = b1.copy(name = "B")

⚠️限制

  • 主构造函数至少要有一个参数;
  • 自动生成函数仅使用主构造参数,类内部额外定义属性不参与 equals/hashCode;

五、其他编译器生成特殊函数

1. 枚举类自动函数

enum class Color{ RED,BLUE }

自动生成:

  • values():返回所有枚举数组
  • valueOf(String):根据名字获取枚举,找不到抛异常

2. 匿名函数 & 带接收者函数字面量

普通Lambda:(T)->R
带接收者Lambda:T.()->R(作用域函数底层基础)

// 带接收者lambda
val block: StringBuilder.() -> Unit = {
    append("test")
}
StringBuilder().block()

3. 工厂函数(约定俗称特殊函数)

companion object 中定义静态创建方法,命名一般 create / newInstance,替代构造函数

class User private constructor(val name: String){
    companion object{
        fun create(name:String):User = User(name)
    }
}

优势:私有构造、方法名语义清晰、支持重载多个创建规则。

六、易混淆特殊函数面试深度问答(背诵版)

Q1: apply 和 also 区别?

  • apply:引用 this,适合操作对象内部属性;
  • also:引用 it,适合读取对象、外部操作(打印日志)。

Q2: let 和 run 区别?

let 使用 it,适合可空对象 ?.let;run 使用 this,适合直接访问对象成员。

Q3: inline 非局部返回原理?

代码直接展开到调用处,return作用域是外层函数;普通lambda是对象,return只能返回lambda。

Q4: 扩展函数为什么不支持多态?

静态解析,编译期根据变量声明类型绑定方法,不依赖运行时实际类型。

Q5: suspend函数底层原理?

每个挂起函数隐式传入 Continuation,挂起点被编译器切分为多个状态机,不需要额外线程。

七、补充边界场景

  1. 顶层函数:直接写在文件内,不属于任何类,编译为静态方法;
  2. main() 特殊入口函数,Kotlin允许无参数顶层main;
  3. operator fun equals():只重载 ==;=== 引用比较不能重载。
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容