前言
如何快速高效的掌握一门学问,建议先阅读下这篇文章关于学习的一些看法。
码字不易,记得关注 + 点赞 + 收藏
该系列的其他文章:Kotlin实战系列之总览
一、函数的定义
在Kotlin中,函数是一等公民, 其有自己的类型,可作为参数传递,赋值。
简单的函数定义示例
//有返回值的函数
fun add(a: Int, b: Int): Int {
return a + b
}
//无返回值的函数
fun add(a: Int, b: Int): Unit {
printIn(a + b)
}
//无返回值的函数 Unit可省略不写 Unit类似java总的void
fun add(a: Int, b: Int) {
printIn(a + b)
}
//单表达式函数: 函数体只有一个表达式,简洁的写法:
fun add(a: Int, b: Int): Int = a + b
函数的主要组成部分:
fun:Kotlin中使用fun关键字定义函数。函数名称: add参数列表: (a: Int, b: Int)- 参数类型: 必须指定,不支持动态类型
- 默认参数: (a: Int = 0, b: Int)
- 变长参数: sum(
varargnumbers: Int) - 具名参数: add(a = 10, b = 20)
函数的类型: (Int,Int) -> Int- 无返回值的类型: Unit 声明函数时可省略
- 有返回值的类型: Int 必须指定其返回值类型
函数体: { return a + b }
二、函数的类型、引用、赋值:
- fun foo() {}:
- 类型:
() -> Unit - 引用:
::foo - 赋值:
val f:() -> Unit = ::foo => val f = ::foo
- 类型:
- fun foo (a: Int): String {...}:
- 类型:
(Int) -> String - 引用:
::foo - 赋值:
val g:(Int) -> String = ::foo => val f = ::foo
- 类型:
- class Foo { fun bar(p0: Int, p1: String):Any {...}}
- 类型:
Foo.(Int,String) -> Any 等价于 (Foo, Int, String) -> Any - 引用:
Foo::bar - 赋值:
val h:(Foo, Int, String) -> Any = Foo::bar => val h = Foo::bar - Foo就是bar方法的
receiver
- 类型:
多返回值 :
fun multiReturnValues(): Triple<Int,Long,String> {
return Triple(1,3L,"hello")
}
//解构
val (a,b,c) = multiReturnValues()
printIn(a+b)
三、匿名函数
匿名函数是无函数名的函数,通常用作高阶函数的参数。
- 基本语法:
fun(参数列表){ 函数体 }- 示例: fun(a: Int, b: Int): Int { return a + b }
fun: 声明函数关键字参数列表: 用括号 () 括起来,参数必须指定类型,参数之间用逗号分隔。函数体: 可以是一个或多个语句。
- 使用场景:
- 通常作为高阶函数的参数,如: list.filter(fun(x) { x > 0 })。
- 赋值: val sum = fun(a: Int, b: Int): Int { return a + b }
- 类型推断:
- Kotlin 编译器可以自动推断匿名函数的参数类型和返回类型。
- 如果函数体只有一个表达式,返回类型也可以自动推断。
四、Lamdba表达式
Lambda表达式是一种匿名函数,是Kotlin中函数式编程的核心概念之一。
- 基本语法:
{ 参数列表 -> 函数体 }如:{ a: Int, b: Int -> a + b }参数: 单个参数可用it关键字代替参数名,多个参数之间用逗号分隔。->: 箭头将参数列表与函数体分隔开来。函数体: 可以是一个或多个语句。
- 使用场景:
- 通常作为高阶函数的参数,如: list.filter { it > 0 }。
- 赋值: val sum = { a: Int, b: Int -> a + b }
- 类型推断:
- Kotlin 编译器可以自动推断Lambda表达式的参数类型和返回类型。
- 如果Lambda表达式只有一个参数时,可用
it关键字代替参数名。
Lamdba与匿名函数的区别:
- 语法
- Lambda表达式:
{ 参数列表 -> 函数体 } - 匿名函数:
fun(参数列表){ 函数体 }
- Lambda表达式:
- 返回值
- Lambda表达式: 隐式地返回最后一个表达式的结果。
- 匿名函数: 可使用return关键字显示地返回值。
- 函数引用
- Lambda表达式: 可直接作为函数参数传递。
- 匿名函数: 需要先复制给一个变量后才能作为参数传递。
五、扩展函数
扩展函数是 Kotlin 中一个非常强大的特性。它允许为已有的类添加新的函数,而无需继承或修改该类的源码。
- 基本语法:
fun 类型名.函数名(参数列表): 返回类型 {// 函数体}
// 为 String 类型添加一个 reversed() 扩展函数
fun String.reversed(): String {
return this.reversed()
}
// 为 Int 类型添加一个 isEven() 扩展函数
fun Int.isEven(): Boolean {
return this % 2 == 0
}
- 扩展函数的特点
- 扩展函数是
静态解析的,编译器会在编译时将扩展函数的调用转换为对应类型的方法调用。 - 扩展函数可访问
public和internal成员,不能访问private和protected成员。 - 如果存在同名的成员函数和扩展函数,
成员函数的优先级更高。 - 扩展函数可以是
顶级函数,也可以定义在类、接口或对象中。
- 扩展函数是
总的来说,扩展函数是 Kotlin 中一个非常强大和灵活的特性,它可以帮助我们编写更加简洁、可读性更强的代码。
六、高阶函数
高阶函数是一种可以将函数作为参数传递或返回值的函数。
这种将函数作为"值"来进行处理的能力是函数式编程的核心特征之一。
示例代码:
fun <T, R> transform(data: T, operation: (T) -> R): R {
//使用 operation 函数对 data 进行处理后返回
return operation(data)
}
- 基本语法:
-
fun: 声明函数的关键字。 -
<T, R>: 泛型T表示参数类型(任意类型); 泛型R表示函数返回值的类型。 -
函数名: transform。 -
参数列表:data:T: 待处理的数据 。operation: 函数名称。(T) -> R): 函数类型, (T): 表示接受一个 T 类型的数据: R: 表示返回 R 类型的结果。
-
返回值: 返回transform函数执行后的结果简洁说明:
transform函数将data传递给operation函数,并返回operation的执行结果。
-
- 使用场景:
- 是将匿名函数或 Lambda 表达式作为参数传递:
val result = transform(42) { it * 2 } // result = 84 - 将已定义的函数引用作为参数传递:
fun square(x: Int) = x * x val result = transform(7, ::square) // result = 49
- 是将匿名函数或 Lambda 表达式作为参数传递:
- 优点:
- 提高代码的灵活性和可重用性。
- 支持函数式编程范式。
- 简化代码结构,使代码更加简洁和易读。
inline关键字:
- 什么是
inline?Kotlin中用于标记某个函数或属性为内联。- 是一种
编译器优化技术,可将被标记的函数或属性直接替换到调用到它的地方,从而避免函数调用的开销。
- 为什么使用
inline?- 函数调用会带来一定的开销,包括:
- 保存和恢复函数调用环境
- 在栈上分配和释放内存
- 跳转指令的执行
- 对于一些小型的、频繁调用的函数,这种开销可能成为性能瓶颈。
- 将这些函数标记为
inline可以消除这些开销,从而提高程序的运行效率。
- 函数调用会带来一定的开销,包括:
inline在高阶函数中的应用:高阶函数通常会将函数作为参数,这可能会增加函数调用的开销。- 因此,
Kotlin建议将高阶函数标记为inline,以优化性能:
inline fun <T, R> transform(data: T, operation: (T) -> R): R { return operation(data) }- 编译器会将
transform函数的调用替换为内联的Lambda表达式,从而消除函数调用的开销。
inline的限制:- 不能内联含有循环、异常处理或
suspend修饰的函数。 - 内联函数的大小不应过大,否则可能导致代码膨胀。
- 不能内联含有循环、异常处理或
常见高阶函数
1、let
public inline fun <T, R> T.let(block: (T) -> R): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE)}
return block(this)
}
inline:Kotlin建议将高阶函数标记为inline,以优化性能。
<T, R> T.let:T表示是为T扩展出一个函数名let(任何类型.let)的函数。R代表是Lambda表达式最后一行返回的类型。
contract {callsInPlace(block, InvocationKind.EXACTLY_ONCE)}:contract块,一种在编译时约束函数行为的机制。callsInPlace(block, InvocationKind.EXACTLY_ONCE):- 表示
block参数必须在函数内部被调用且只能调用一次。 - 这意味着该函数必须确保
block参数被精确地调用一次。
- 表示
block: (T) -> R:block: 表示Lambda表达式名称。T: 表示输入参数。
R: 表示输出参数,即Lambda表达式最后一行返回推断的类型。
let函数接受一个lambda表达式作为参数,该表达式接受一个 T 类型的参数,并返回一个R类型的结果(Lambda表达式最后一行返回的类型)。
- 主要作用:
1、处理可空对象: 检查对象是否为null,如果不为null则对其进行处理。
val name: String? = "Alice" name?.let { println("Name length: ${it.length}") }2、链式调用:
val result = "hello".let { it.uppercase() }.let { it.reversed() } println(result) // Output: "OLLEH"3、局部变量作用域:
val person = Person("Alice", 25) person.let { println("Name: ${it.name}, Age: ${it.age}") }4、返回值转换:
val upperCaseName = person.name?.let { it.toUpperCase() }
2、apply
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE)}
block()
return this
}
apply函数接受一个lambda表达式作为参数,该lambda表达式是一个扩展函数,它以 T 类型的实例作为接收者。该函数会将调用它的对象作为参数传递给这个lambda表达式,然后返回这个对象本身。
- 主要作用:
1、对象初始化和配置:
val person = Person("John Doe") .apply { age = 30 address = "123 Main St" }2、Builder 模式:
val message = StringBuilder() .append("Hello, ") .append("world!") .apply { toString() }3、链式调用:
val result = person ?.address ?.city ?.apply { toUpperCase() }4、日志记录:
fun processData(data: Data) { data.apply { println("Processing data: $this") } // 处理数据的逻辑 }
3、also
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {callsInPlace(block, InvocationKind.EXACTLY_ONCE)}
block(this)
return this
}
also函数接受一个lambda表达式作为参数,该lambda表达式接受一个 T 类型的参数。该函数会将调用它的对象作为参数传递给这个lambda表达式,然后返回这个对象本身。
- 主要作用:
1、进行副作用操作:
val person = Person("John Doe") .also { println("Created person: $it") }2、错误处理:
fun processData(data: Data?) { data?.also { d -> if (d.value < 0) { throw IllegalArgumentException("Invalid data: $d") } // 处理数据的逻辑 } }3、链式调用:
val result = person ?.address ?.city ?.also { println("City: $it") } ?.length4、日志记录:
fun processData(data: Data) { data.also { println("Processing data: $it") } // 处理数据的逻辑 }
4、run
public inline fun <T, R> T.run(block: T.() -> R): R {
contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
return block()
}
run函数接受一个lambda表达式作为参数,该lambda表达式是一个扩展函数,它以 T 类型的实例作为接收者。该函数会将调用它的对象作为参数传递给这个lambda表达式,然后返回lambda表达式的结果。
- 主要作用:
1、对象创建和初始化:
val message = run { val builder = StringBuilder() builder.append("Hello, ") builder.append("world!") builder.toString() }2、局部变量作用域:
fun processData(data: Data?) { data?.run { val length = value.length // 在这个作用域内可以访问 this 变量 println(length) } }3、链式调用:
val result = person ?.address ?.city ?.run { toUpperCase() length }4、条件执行:
val length = data?.run { if (value < 0) { throw IllegalArgumentException("Invalid data: $this") } value.length }
5、with
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {callsInPlace(block, InvocationKind.EXACTLY_ONCE)}
return receiver.block()
}
with函数接受两个参数: 一个 receiver 对象和一个 lambda 表达式。这个 lambda 表达式是一个扩展函数,它以 receiver 对象为接收者。该函数会将 receiver 对象作为参数传递给这个 lambda 表达式,然后返回 lambda 表达式的结果。
- 主要作用:
1、简化对象访问:
val person = Person("John Doe") with(person) { println("Name: $name") println("Age: $age") }2、Builder 模式:
val message = with(StringBuilder()) { append("Hello, ") append("world!") toString() }3、嵌套调用:
val result = with(person) { with(address) { "$city, $country" } }4、条件执行:
val length = with(data) { if (value < 0) { throw IllegalArgumentException("Invalid data: $this") } value.length }
6、takeIf
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)}
return if (predicate(this)) this else null
}
takeIf函数接受一个 lambda 表达式作为参数,并根据 lambda 表达式的返回结果决定是否返回原值或 null。
- 主要作用:
1、参数校验:
fun divide(a: Int, b: Int): Int? { // 检查除数是否为 0,如果不为 0 则执行除法操作并返回结果,否则返回 null。 return b.takeIf { it != 0 }?.let { a / it } }2、数据过滤:
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) //过滤集合中的元素,仅保留满足条件的元素。 val evenNumbers = numbers.filter { it.takeIf { it % 2 == 0 } != null } println(evenNumbers) // Output: [2, 4, 6, 8, 10]3、空值处理:
val name: String? = "Alice" //检查 name 是否为空字符串,如果不为空则返回字符串长度,否则返回 null。 val nameLength = name.takeIf { it.isNotEmpty() }?.length println(nameLength) // Output: 54、链式调用:
val user = User("Alice", 25) //takeIf 可与其他函数如 let、run 等配合使用,形成链式调用,以实现更复杂的逻辑。 val formattedName = user.takeIf { it.age >= 18 }?.let { "${it.name} (Adult)" } println(formattedName) // Output: Alice (Adult)
7、takeUnless
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
contract { callsInPlace(predicate, InvocationKind.EXACTLY_ONCE) }
return if (!predicate(this)) this else null
}
takeUnless函数 接受一个 lambda 表达式作为参数,该 lambda 表达式返回一个布尔值。如果 lambda 表达式返回 false,takeUnless 就会返回原值,如果 lambda 表达式返回 true,takeUnless 就会返回 null。
- 主要作用:
1、参数校验:
fun divide(a: Int, b: Int): Int? { //检查除数是否为 0。如果除数不为 0,我们就使用 let 函数执行除法操作并返回结果。 //如果除数为 0,takeUnless 会返回 null,从而避免了除法异常的发生。 return b.takeUnless { it == 0 }?.let { a / it } } fun main() { val result = divide(10, 2) // 5 val resultWithZeroDivisor = divide(10, 0) // null2、数据过滤:
val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) //使用 filter 函数配合 takeUnless 来过滤出集合中的奇数。 //takeUnless 会返回 null 来排除偶数,最终 filter 函数只保留了奇数。 val oddNumbers = numbers.filter { it.takeUnless { it % 2 == 0 } != null } println(oddNumbers) // Output: [1, 3, 5, 7, 9]3、空值处理:
val name: String? = null //检查 name 是否为空或空字符串。 //如果 name 为空,takeUnless 会返回 null,否则返回原值。 val nameLength = name.takeUnless { it.isNullOrEmpty() }?.length println(nameLength) // Output: null4、链式调用:
val user = User("Alice", 17) //takeUnless 可与let、run 等配合使用,形成链式调用,以实现更复杂的逻辑。 val formattedName = user.takeUnless { it.age < 18 }?.let { "${it.name} (Adult)" } println(formattedName) // Output: null
8、lazy
Kotlin 中的 lazy 函数是一种非常有用的延迟初始化机制。在需要时才初始化一个值,从而提高应用程序的性能和内存利用率。
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
它接受一个 lambda 表达式作为参数,该 lambda 表达式会在第一次访问该值时被调用,并负责初始化该值。lazy 函数会返回一个 Lazy<T> 实例,该实例可以用于访问初始化后的值。
- 主要作用:
1、基本用法:
val lazyValue: Int by lazy { println("Initializing lazyValue") 42 } fun main() { println("Before accessing lazyValue") //第一次访问时,它会执行初始化 lambda 表达式,输出 "Initializing lazyValue" 并返回值 42。 println(lazyValue) // Output: Initializing lazyValue, 42 //再次访问时,不会再次执行初始化过程,而是直接返回之前初始化的值。 println(lazyValue) // Output: 42 (no additional initialization) }2、线程安全:
val lazyValue: String by lazy { println("Initializing lazyValue on thread: ${Thread.currentThread().name}") "Hello, Kotlin!" } fun main() { // 在两个不同的线程中访问 lazyValue Thread { println("Thread 1: $lazyValue") }.start() Thread { println("Thread 2: $lazyValue") }.start() // 输出: // Initializing lazyValue on thread: Thread-0 // Thread 1: Hello, Kotlin! // Thread 2: Hello, Kotlin! //创建了两个线程来访问 lazyValue。尽管两个线程同时访问 lazyValue, //但初始化过程只会在其中一个线程中执行,确保了线程安全。 }3、Scope 和同步:
//使用LazyThreadSafetyMode.SYNCHRONIZED 模式来确保初始化过程的同步。 //这意味着在初始化过程中,对 lazyValue 的所有访问都将被同步,以确保线程安全。 val lazyValue: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { println("Initializing lazyValue on thread: ${Thread.currentThread().name}") "Hello, Kotlin!" }