重学Kotlin(三)函数
一、函数的基本定义
在 Kotlin 中,函数用 fun 关键字声明:
fun sayHello() {
println("Hello Kotlin")
}
fun是关键字sayHello是函数名()表示参数列表(这里为空){}内是函数体
调用函数时:
sayHello()
二、带参数与返回值的函数
带参数
fun greet(name: String) {
println("Hello, $name")
}
调用:
greet("Tom") // 输出:Hello, Tom
带返回值
fun add(a: Int, b: Int): Int {
return a + b
}
Kotlin 支持类型推断:
fun add(a: Int, b: Int) = a + b // 推断返回类型 Int
三、默认参数 & 命名参数
默认参数:
fun greet(name: String = "World") {
println("Hello, $name")
}
greet() // Hello, World
greet("Tom") // Hello, Tom
命名参数:
fun connect(host: String, port: Int) {
println("Connecting to $host:$port")
}
connect(port = 8080, host = "localhost")
四、可变参数(vararg)
允许传入任意数量的参数:
fun sumAll(vararg numbers: Int): Int {
return numbers.sum()
}
println(sumAll(1, 2, 3, 4)) // 10
五、局部函数(函数里定义函数)
fun outer() {
fun inner() {
println("I'm inner")
}
inner()
}
局部函数只能在外部函数里使用。
六、顶层函数(不需要类)
在 Kotlin 中,函数不必放在类里,可以直接定义在文件顶层:
// MyUtils.kt
fun printHello() = println("Hello")
然后其他文件中可以直接导入使用。
七、扩展函数
给现有类“添加新方法”,而无需继承:
fun String.addSmile(): String {
return this + "😊"
}
println("Hello".addSmile()) // Hello😊
八、Lambda 表达式与高阶函数
Kotlin 的函数可以作为参数或返回值。
注意: Lambda 表达式的详细介绍见上一章。
高阶函数示例:
fun operate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
Lambda 传入:
val result = operate(3, 4) { x, y -> x + y }
println(result) // 7
九、函数类型
函数也有类型:
val f: (Int, Int) -> Int = { a, b -> a + b }
十、内联函数(inline)
用于减少函数调用的性能开销(常与 Lambda 搭配):
inline fun runTwice(action: () -> Unit) {
action()
action()
}
十一、匿名函数
除了 Lambda,也可以用 fun 声明匿名函数:
val multiply = fun(x: Int, y: Int): Int = x * y
println(multiply(2, 3))
十二、函数与方法(成员函数)的区别
1. 从定义上区分
函数(Function): 独立存在的可执行代码块。
方法(Method): 属于某个类或对象的函数。
2. 从字节码角度区分
所有 Kotlin "函数" 都会变成 JVM 方法(method)。
区别只在于:
- 顶层函数、扩展函数 → 编译成 静态方法
- 类中函数 → 编译成 实例方法(带 this)
fun test() {}
fun String.testExt() {}
public final static test()V
L0
LINENUMBER 15 L0
RETURN
MAXSTACK = 0
MAXLOCALS = 0
public final static testExt(Ljava/lang/String;)V
// annotable parameter count: 1 (invisible)
@Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
L0
ALOAD 0
LDC "<this>"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 19 L1
RETURN
L2
LOCALVARIABLE $this$testExt Ljava/lang/String; L0 L2 0
MAXSTACK = 2
MAXLOCALS = 1
3. 函数的类型
class Test {
fun test0(a: Int, b: Long): Any {
print("a=$a,b=$b")
return a + b
}
fun test1(c: (Test, Int, Long) -> Any): Any {
return c(this, 0, 1L)
}
fun test2(c: Test.(Int, Long) -> Any): Any {
return c(this, 0, 2L)
}
fun test3(c: Function3<Test, Int, Long, Any>): Any {
return c(this, 0, 3L)
}
fun main() {
test1(Test::test0) // a=0,b=1
test2(Test::test0) // a=0,b=2
test3(Test::test0) // a=0,b=3
}
}
以下三种写法是等效的,都能表示方法的类型:
(Test, Int, Long) -> AnyTest.(Int, Long) -> AnyFunction3<Test, Int, Long, Any>
十三、函数的引用
函数引用就是把函数当作值传递。 用符号
::来表示。
1. 普通函数引用:直接调用
fun add(a: Int, b: Int) = a + b
val sum: (Int, Int) -> Int = ::add
println(sum(2, 3)) // 输出 5
2. 成员函数引用(带接收者):
class Person(val name: String) {
fun sayHello(age: Int) = "$name is $age years old"
}
val p = Person("Tom")
// 引用成员函数
val f: Person.(Int) -> String = Person::sayHello
// 调用方式 1:通过接收者
println(p.f(20)) // Tom is 20 years old
// 调用方式 2:使用 invoke
println(f.invoke(p, 25)) // Tom is 25 years old
3. 构造函数引用:
class Person(val name: String, val age: Int)
val creator: (String, Int) -> Person = ::Person
val p = creator("Tom", 20)
println(p.name) // Tom
4. 静态成员函数引用
class MathUtil {
companion object {
fun square(x: Int) = x * x
}
}
// 函数引用
val sq: (Int) -> Int = MathUtil.Companion::square
println(sq(4)) // 16
十四、内联函数
Kotlin 中使用 inline 修饰的函数,会在编译期把 函数体代码直接拷贝到调用处,而不是通过函数调用跳转执行。
1. 为什么要使用内联函数
结论:能节省以下开销
- 函数调用开销
- Lambda 创建开销
- 对象分配开销
举例说明:
(1)不使用内联函数
会创建 Lambda 函数,并且有两次函数调用:
fun test() {
println("Hello")
runBlock {
println("World")
}
}
fun runBlock(block: () -> Unit) {
block()
}
// 为方便理解,把字节码反编译成了java
public static final void test() {
System.out.println("Hello");
runBlock(TestKt::test$lambda$0);
}
public static final void runBlock(@NotNull Function0 block) {
Intrinsics.checkNotNullParameter(block, "block");
block.invoke();
}
private static final Unit test$lambda$0() {
System.out.println("World");
return Unit.INSTANCE;
}
(2)使用内联函数
fun test() {
println("Hello")
runBlock {
println("World")
}
}
inline fun runBlock(block: () -> Unit) {
block()
}
public static final void test() {
System.out.println("Hello");
int $i$f$runBlock = 0;
int var1 = 0;
System.out.println("World");
}
public static final void runBlock(@NotNull Function0 block) {
Intrinsics.checkNotNullParameter(block, "block");
int $i$f$runBlock = 0;
block.invoke();
}
其实发生了两次内联:
- 把参数内联到内联函数中
- 内联函数内联到调用处
并且不会生成 Lambda 函数。
2. 内联函数的缺点
- 代码体积变大(代码膨胀):因为每次调用都展开
- 不能递归:inline 不允许递归函数(会无限展开)
- 过多 inline 会增大 dex / apk 大小:所以并不是所有函数都应该 inline
3. 内联函数使用场景
结合以上优缺点,内联函数需要在适当的时候去使用。典型的使用场景:
(1)高阶函数(最常用)
- 例如:
let、apply、also、run、with全是 inline - 减少 Lambda 开销
- 提高性能
(2)控制结构 DSL
- 例如:
repeat、measureTime、lock、transaction等
(3)协程的 suspendCancellableCoroutine
- 内联在协程框架中大量使用
4. inline 带来的额外能力:non-local return
inline fun run(block: () -> Unit) {
block()
}
fun foo() {
run {
println("run")
return // ← 直接从 foo() 返回,普通高阶函数无法做到
}
println("foo") // 这一行不会被执行
}
这是因为 lambda 被直接展开到调用处(普通高阶函数是无法做到的)
5. crossinline 与 noinline
(1)crossinline
禁止 lambda 中使用 return,但仍然 inline:
inline fun doJob(crossinline block: () -> Unit) {
Thread { block() }.start() // 不能 non-local return
}
(2)noinline
禁止 inline(不展开):
inline fun funA(a: () -> Unit, noinline b: () -> Unit) {}
a会被 inlineb不会被 inline,可以传到别的地方当普通对象
Kotlin 内联函数(inline function) 有一些非常明确的限制,否则会引发编译错误或逻辑 bug。
6. 内联函数的限制
(1)不能递归(最重要)
因为 inline 会在编译期展开,如果递归则会无限展开 → 编译失败或爆炸。
inline fun f() = f() // ❌ 递归,不能 inline
(2)不能 inline open / override 的函数
因为 open/override 依赖 运行时动态分派(虚方法表),与 inline 的“编译期展开”冲突。
open inline fun test() {} // ❌
override inline fun test() {} // ❌
原因:虚函数在运行时才能确定实际类型,而 inline 必须在编译期就完全确定。
(3)inline 函数不能包含局部类(local class)
因为局部类依赖函数栈帧,inline 后栈结构改变。
inline fun foo() {
class LocalClass {} // ❌ 不能定义局部类
}
(4)inline 函数不能包含局部的 non-captured object
比如:
inline fun foo() {
object : Runnable { // ❌
override fun run() {}
}
}
因为 inline 展开后会复制 object 声明,导致行为不一致。
(5)不能在 inline 的 lambda 中使用非 inline 的 return
inline fun run(block: () -> Unit) {
block()
}
fun test() {
run {
return // ← non-local return(允许)
}
}
如果 lambda 不能 non-local return 就会报错,需要 crossinline。
(6)无法 inline 的参数要标记 noinline
如果你传的 lambda 要:
- 存储到变量
- 传给另一个函数
- 放到另一个对象里
- 作为属性保存
就不能 inline,必须加 noinline:
inline fun foo(a: () -> Unit, noinline b: () -> Unit) {
doSomething(b) // ❌ 如果不加 noinline 会报错
}
因为 inline 的 lambda 在调用处没有“对象”,所以不能当作对象处理。
(7)reified 类型参数必须依赖 inline
也可以当作一种限制:
fun <T> test() { } // ❌ 无法获得 T 的真实类型
inline fun <reified T> test() { } // ✔ 必须 inline
十五、常用高阶函数
1. 作用域函数
举例分析 let 和 run 函数
// 将调用者当做参数传递
public inline fun <T, R> T.let(block: (T) -> R): R {}
// 类似拓展函数,像在给对象添加一个临时方法
public inline fun <T, R> T.run(block: T.() -> R): R {}
class Person {
var name = "Tom"
var age = 28
override fun toString(): String {
return "Person(name='$name', age=$age)"
}
}
fun main() {
var person: Person? = Person()
val f1: Person.() -> Unit = {
this.name = "" //this 可省略
println(name)
}
val f2: (Person) -> Unit = {
it.age = 30 //it 代表当前 Person 对象
}
// 由上文十二节函数的类型分析, f1 和 f2 函数类型是完全等效的,所以可以进行如下调用
person?.let(f1)
person?.let(f2)
// 也可以直接在 let 的 lambda 中调用,但需要满足函数类型为(Person) -> Unit
val age = person?.let {
it.age = 30
it.age // 最后一行为返回值
}
// 同样的,我们也可以使用 run 来调用它们
person?.run(f1)
person?.run(f2)
val name = person?.run {
name = "Jerry"
name
}
}
其他常见作用域函数对比
| 函数 | this / it | 返回值 | 典型用途 |
|---|---|---|---|
let | it | 最后一行 | 对可空对象做安全操作、链式调用 |
run | this | 最后一行 | 初始化对象、转换值 |
apply | this | this(对象本身) | 配置对象(构建对象) |
also | it | this(对象) | 对象链式调试、side effect |
with | this | 最后一行 | 对已有对象做一系列操作 |
takeIf / takeUnless | it | 条件成立返回对象 | 过滤对象是否继续链式调用 |
2. 集合常用高阶函数
| 函数 | 含义 |
|---|---|
| map | 映射 |
| filter | 过滤 |
| flatMap | 展开 |
| reduce | 累积(无初始值) |
| fold | 累积(有初始值) |
| groupBy | 分组 |
| sortedBy | 排序 |
| any/all/none | 判断 |
| find | 找一个 |
十六、从字节码角度理解 Kotlin 函数也是一种类型
Kotlin 函数类型在 JVM 上被编译成接口实例:
(Int) -> String→Function1<Int, String>(A, B) -> C→Function2<A, B, C>
所以 Kotlin 函数“是类型”→ 实际就是一个 接口的实现类。
例如:
val f: (Int) -> String = { it.toString() }
会编译成:
Function1<Integer, String> f = new Function1<Integer, String>() {
@Override
public String invoke(Integer it) {
return it.toString();
}
};
也就是匿名类。
十七、SAM(Single Abstract Method Interface)
1. SAM 是什么
SAM = 单一抽象方法接口(Single Abstract Method Interface)
也就是:只有一个抽象方法的接口(或 Java 抽象类)。
Java 里的例子:
interface Runnable {
void run();
}
只有一个抽象方法 run() → SAM 类型。
2. Kotlin 为什么需要 SAM?
因为 Kotlin 里 Lambda 本质是 FunctionN 对象:
() -> Unit // Function0<Unit>
(Int) -> String // Function1<Int, String>
但很多 Java 库要求传入一个接口实例,比如 Runnable、Callable、OnClickListener。
为了让 Kotlin 能像 Java 一样写得简洁,比如:
Thread { println("Hello") }
就需要把:
lambda → SAM 接口对象
这叫做 SAM 转换。
3. Kotlin 的 SAM 转换规则
只能作用于 Java 接口
不能作用于 Kotlin 接口(除非 fun interface)。
例如:
button.setOnClickListener { ... } // ✅ OK
但你自己写的 Kotlin 接口:
interface A { fun run() }
却不能:
val a = A { println("hi") } // ❌ 错误
因为 Kotlin 默认不做 SAM 转换。
4. Kotlin 要启用 SAM,需要 fun interface
Kotlin 加了关键字:
fun interface Foo {
fun bar()
}
只有加了 fun 才是 SAM 接口。
现在你可以这样写:
val foo: Foo = { println("hello") }
5. Kotlin 为什么默认不支持接口 SAM?
因为 Kotlin 接口可以有:
- 多个抽象方法
- 默认方法
- 扩展方法
- 属性
担心歧义,所以必须手动标记 fun interface。
6. SAM 转换是什么样的?
例子:
Thread { println("running") }
Kotlin 会编译成:
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("running");
}
})
7. SAM 转换与 Lambda 的本质区别
Lambda 是一个函数对象(FunctionN)
类型:
() -> Unit
SAM 转换结果是一个接口实例
类型:
Runnable
所以它们完全不是一回事!
8. SAM 与 Kotlin 的函数类型不兼容
不能这样:
fun test(r: Runnable) { }
val f: () -> Unit = { }
test(f) // ❌ 不能自动 SAM
必须:
test { } // OK:直接写 lambda 才会 SAM 转换
9. SAM 转换可能带来的隐患
// java类
public class EventManager {
private final List<onEventListener> listeners = new ArrayList<>();
public interface onEventListener {
void onEvent(String event);
}
public void registerEvent(onEventListener listener) {
listeners.add(listener);
}
public void unregisterEvent(onEventListener listener) {
listeners.remove(listener);
}
}
val eventManager = EventManager()
// 本质是匿名内部类
eventManager.registerEvent(object : EventManager.onEventListener {
override fun onEvent(event: String?) {
println(event)
}
})
// SAM转化为 lambda 形式
eventManager.registerEvent {
println("$it")
}
// ❌普通的 lambda 表达式, 无法作为接口实现,类型是 (String) -> Unit
val onEvent1 = { event: String ->
println()
}
eventManager.registerEvent(onEvent1)
eventManager.unregisterEvent(onEvent1) // 无法注销onEvent1
// ✅lambda 表达式实现接口
val onEvent2 = EventManager.onEventListener { event ->
println(event)
}
// ✅匿名对象实现接口
val event3 = object : EventManager.onEventListener {
override fun onEvent(event: String?) {
println(event)
}
}