Kotlin实战系列之函数

266 阅读10分钟

image.png

前言

如何快速高效的掌握一门学问,建议先阅读下这篇文章关于学习的一些看法

码字不易,记得关注 + 点赞 + 收藏

该系列的其他文章: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(vararg numbers: 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表达式: 隐式地返回最后一个表达式的结果。
    • 匿名函数: 可使用return关键字显示地返回值。
  • 函数引用
    • Lambda表达式: 可直接作为函数参数传递。
    • 匿名函数: 需要先复制给一个变量后才能作为参数传递。

五、扩展函数

扩展函数是 Kotlin 中一个非常强大的特性。它允许为已有的类添加新的函数,而无需继承或修改该类的源码

  • 基本语法: fun 类型名.函数名(参数列表): 返回类型 {// 函数体}
// 为 String 类型添加一个 reversed() 扩展函数
fun String.reversed(): String {
    return this.reversed()
}
// 为 Int 类型添加一个 isEven() 扩展函数
fun Int.isEven(): Boolean {
    return this % 2 == 0
}
  • 扩展函数的特点
    • 扩展函数是静态解析的,编译器会在编译时将扩展函数的调用转换为对应类型的方法调用。
    • 扩展函数可访问publicinternal成员,不能访问privateprotected成员。
    • 如果存在同名的成员函数和扩展函数,成员函数的优先级更高
    • 扩展函数可以是顶级函数,也可以定义在类、接口或对象中。

总的来说,扩展函数是 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
      
  • 优点:
    • 提高代码的灵活性和可重用性。
    • 支持函数式编程范式。
    • 简化代码结构,使代码更加简洁和易读。

inline关键字:

  1. 什么是inline ?
    • Kotlin中用于标记某个函数或属性为内联。
    • 是一种编译器优化技术,可将被标记的函数或属性直接替换到调用到它的地方,从而避免函数调用的开销
  2. 为什么使用inline ?
    • 函数调用会带来一定的开销,包括:
      • 保存和恢复函数调用环境
      • 在栈上分配和释放内存
      • 跳转指令的执行
    • 对于一些小型的、频繁调用的函数,这种开销可能成为性能瓶颈。
    • 将这些函数标记为inline可以消除这些开销,从而提高程序的运行效率。
  3. inline在高阶函数中的应用:
    • 高阶函数通常会将函数作为参数,这可能会增加函数调用的开销
    • 因此,Kotlin建议将高阶函数标记为inline,以优化性能:
    inline fun <T, R> transform(data: T, operation: (T) -> R): R {
        return operation(data)
    }
    
    • 编译器会将transform函数的调用替换为内联的Lambda表达式,从而消除函数调用的开销。
  4. 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") }
      ?.length
    
    • 4、日志记录:
    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: 5
    
    • 4、链式调用:
    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 表达式返回 falsetakeUnless 就会返回原值,如果 lambda 表达式返回 truetakeUnless 就会返回 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) // null
    
    • 2、数据过滤:
    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: null
    
    • 4、链式调用:
    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!"
    }