快速入门Kotlin②控制流&函数

控制流

if

if是一个表达式,可以用于条件判断和控制流。

Kotlin 复制代码
fun main() {
    // 传统用法
    val a = 1
    val b = 2
    var max = a
    if (a < b) max = b

    // With else
    var max2: Int
    if (a > b) {
        max2 = a
    } else {
        max2 = b
    }

    // 作为表达式
    val max3 = if (a > b) a else b
}

when

when 表达式取代了Java 的 switch 语句,可以用任意表达式(而不只是常量,比如in/is/函数)作为分支条件。

Kotlin 复制代码
fun main() {
    val obj: Any  = ""
    val result = when (obj) {
        1          -> "One"
        //假如很多分支需要用相同的方式处理,则可以把多个分支条件放在一起
        2, 3       -> "Two or three"
        "Hello"    -> "Greeting"
        //任意表达式(而不只是常量)作为分支条件(in/is/函数)
        is Long    -> "Long"
        !is String -> "Not a string"
        else       -> "Unknown"
    }
}

for

for 循环可以对任何提供迭代器(iterator)的对象进行遍历。

Kotlin 复制代码
fun main() {
    val list = listOf("apple", "banana", "otherfruit")
    for (item in list) {
        println(item)
    }

    //通过索引遍历一个数组或者一个 list
    for (index in list.indices) {
        println("item at $index is ${list[index]}")
    }

    //区间表达式
    for (i in 1..3) {
        println(i)
    }
    for (i in 6 downTo 0 step 2) {
        println(i)
    }
}

while

whiledo ..while 照常使用。

Kotlin 复制代码
fun main() {
    //while 与 do..while 照常使用
    val list = listOf("apple", "banana", "otherfruit")
    var index = 0
    while (index < list.size) {
        println("item at $index is ${list[index]}")
        index++
    }

    index = 0
    do {
        println("item at $index is ${list[index]}")
        index++
    }while (index < list.size)
}

返回和跳转

Kotlin 有三种结构化跳转表达式:return、break和continue。

  • return 。默认从最直接包围它的函数或者++匿名函数++返回。

  • break。终止最直接包围它的循环。

  • continue。继续下一次最直接包围它的循环。

Kotlin 复制代码
fun main() {
    findNumberByReturn(3)
    forByBreak();
    forByContinue()
}

fun findNumberByReturn(target: Int): Boolean {
    val numbers = listOf(1, 2, 3, 4, 5)
    for (number in numbers) {
        if (target == number) {
            return true // 结束函数并返回找到的值
        }
        println(number)
    }
    return false  // 循环结束后未找到目标值,返回 -1
}

fun forByBreak() {
    val numbers = listOf(1, 2, 3, 4, 5)
    for (number in numbers) {
        if (number == 3) {
            break  // 循环终止
        }
        println(number)
    }
    //输出:1 2
}

fun forByContinue() {
    val numbers = listOf(1, 2, 3, 4, 5)
    for (number in numbers) {
        if (number == 3) {
            continue  // 跳过当前迭代,继续下一次迭代
        }
        println(number)
    }
    //输出:1 2 4 5
}

函数

参数

参数:函数参数定义 name: type。参数用逗号隔开,每个参数必须有显式类型。 默认参数 :函数参数可以有默认值,当省略相应的参数时使用默认值。 具名参数:具名参数是一种在函数调用时指定参数名称的方式。在函数调用时使用 参数名 = 值 的形式来传递参数。

可变参数:可以用 vararg 修饰符标记,通常是最后一个参数。允许将可变数量的参数传递给函数。 默认参数特殊Case:如果在默认参数之后的最后一个参数是 lambda 表达式,那么它既可以作为具名参数在括号内传入,也可以在括号外传入。

Kotlin 复制代码
fun main() {
    println(getResult(2, 3)) //输出:6
    //默认参数
    println(getResult(2)) //输出:2

    //具名参数
    foo(baz = 1) // 输出:bar is 0, baz is 1

    //可变参数
    println(asList(1, 2, 3)) //输出:[1, 2, 3]
}

// Kotlin 中的函数使用 fun 关键字声明。
fun getResult(x: Int, y: Int = 1) : Int {
    return x * y
}

//具名参数举例
fun foo(
    bar: Int = 0,
    baz: Int,
) {
    println("bar is $bar, baz is $baz")
}

fun<T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) {
        result.add(t)
    }
    return result
}

返回类型

具有块代码体的函数必须始终显式指定返回类型,除非他们旨在返回 Unit。因为这样的函数在代码体中可能有复杂的控制流,并且返回类型对于读者(有时甚至对于编译器)是不明显的。

函数作用域&类型

  1. 顶层函数,无需创建类来保存函数。

  2. 局部作用域:嵌套函数。

  3. 成员函数:在类或对象内部定义的函数。

  4. 扩展函数:可以在不继承或修改类的情况下,为类添加额外的行为和功能。扩展函数是静态解析的,它并没有对类进行实际的修改或继承。扩展函数的作用域仅限于定义它的文件内。

  5. 泛型函数:可以在函数中使用类型参数的特殊函数,它增加了函数的灵活性和重用性。

Kotlin 复制代码
//顶层函数,无需创建类来保存函数。
fun main() {
    Sample().foo() // 创建类 Sample 实例并调用 foo,输出:Foo

    outerFunction() // 嵌套函数。输出:Outer function Nested function

    val str = "hello"
    println(str.reverseStr()) //输出:olleh
    
    println(createList("apple", "banana", "orange")) //输出:[apple, banana, orange]
    println(createList(1, 2, 3, 4, 5)) //输出:[1, 2, 3, 4, 5]
}

class Sample {
    //成员函数:类或对象内部定义的函数。
    fun foo() { println("Foo") }
}

fun outerFunction() {
    println("Outer function")

    //嵌套函数:可以在外部函数内部进行封装,并且只在外部函数内部可见和使用。
    fun nestedFunction() {
        println("Nested function")
    }

    nestedFunction()
}

// 扩展函数:将字符串反转
fun String.reverseStr(): String {
    return this.reversed()
}

//泛型函数: `createList` 是一个泛型函数,它接受可变数量的参数 `items`,类型为 `T`。函数的返回类型为 `List<T>`,即根据传递的参数类型返回相应类型的列表。
fun <T> createList(vararg items: T) : List<T> {
    return items.toList()
}

高阶函数和lambda 表达式

高阶函数

是将函数用作参数或返回值的函数,更加灵活。

  • map: 函数用于对集合中的每个元素应用给定的转换函数,并返回新集合。

  • filter:函数用于根据给定的条件函数过滤集合中的元素,返回新集合。

  • forEach: 函数用于对集合中的每个元素应用给定的操作函数,没有返回值。

Kotlin 复制代码
fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    val convertNumbers = numbers.map { it * it }

    val evenNumbers = numbers.filter { it % 2 == 0 }
    println(convertNumbers) //输出:[1, 4, 9, 16, 25]
    println(evenNumbers) //输出:[2, 4]
    numbers.forEach { print(it) } //输出:12345
}

函数类型

函数类型声明指定了函数参数和返回值的类型,以及函数的参数个数和参数类型。语法如下:

(parameters) -> returnType

  • parameters 是函数的参数列表,可以包含零个或多个参数,在声明中使用参数名和类型来指定。

  • returnType 是函数的返回值类型,用于指定函数的返回结果类型。

常见的函数类型声明如下:

Kotlin 复制代码
//不带参数的函数类型
() -> Unit
//带有一个整数参数的函数类型
(Int) -> Unit
//带有两个字符串参数和一个整数返回值的函数类型
(String, String) -> Int
//带有一个整数参数和一个字符串返回值的函数类型
(Int) -> String

代码示例:

Kotlin 复制代码
fun main() {
   applyFunction(::square) //输出结果 result: 1764
}

/**
 * applyFunction 是一个高阶函数,它接受一个函数类型参数 function。
 * applyFunction 函数内部调用传递的函数类型参数,并将结果打印出来。
 */
fun applyFunction(function: (Int) -> Int) {
    val result = function(42)
    println("result: $result")
}

/**
 * 我们将 square 函数作为参数传递给 applyFunction 函数。
 */
fun square(x :Int) : Int {
    return x * x
}

Lambda 表达式

Lambda 表达式可以理解为轻量级的函数字面量,提供更简洁的语法。

基本语法如下:

Kotlin 复制代码
val sum: (Int, Int) -> Int = { x: Int, y: Int -> x + y }
  • lambda 表达式总是括在花括号中。

  • 完整语法形式的参数声明放在花括号内,并有可选的类型标注。

  • 函数体跟在一个 -> 之后。

  • 如果推断出的该 lambda 的返回类型不是 Unit,那么该 lambda 主体中的最后一个(或可能是单个)表达式会视为返回值。

可以优化为:

Kotlin 复制代码
val sum = { x: Int, y: Int -> x + y }

代码示例:

Kotlin 复制代码
fun main() {
    //Lambda 表达式没有参数
    //定义了一个没有参数的 Lambda 表达式,并将其赋值给类型为 () -> Unit 的变量 greet。
    val great = { println("hello, kotlin") }
    great()  //输出:hello, kotlin

    //Lambda 表达式带有一个整数参数
    //定义了一个带有一个整数参数的 Lambda 表达式,并将其赋值给类型为 (Int) -> Int 的变量 square
    val square: (Int) -> Int = { y -> y * y }
    println(square(5))  //输出:25

    //Lambda 表达式带有多个参数
    //定义了一个带有两个整数参数的 Lambda 表达式,并将其赋值给类型为 (Int, Int) -> Int 的变量 add。
    val add = { x: Int, y: Int -> x + y }
    println(add(3, 7))  //输出:10
    //亦或者写为:
    val add1: (Int, Int) -> Int = { x, y -> x + y }
    println(add1(3, 7))  //输出:10
}

最后一个参数是 Lambda 表达式:可以使用末尾 Lambda 表达式的语法糖。这种语法糖允许我们在调用函数时将 Lambda 表达式放在圆括号外部,增强了代码的可读性。

Kotlin 复制代码
fun performOperation(value: Int, operation: (Int) -> Int): Int {
    return operation(value)
}

fun main() {
    val result = performOperation(5) { number ->
        number * 2
    }
    println("Result: $result") //输出:Result: 10
}

匿名函数

Lambda表达式通常比匿名函数更简洁。但匿名函数在某些情况下可能更适合,比如需要显式指定返回类型或需要包含多个语句的情况。

基本语法:

Kotlin 复制代码
fun(x: Int, y: Int): Int {
    return x + y
}

//可以优化为:
//显式指定返回值为Int
fun(x: Int, y: Int): Int = x + y

代码示例:

Kotlin 复制代码
fun main() {
    val numbers = listOf(1, 2, 3, 4, 5)
    // 匿名函数接受一个整数参数 num,并在函数体中定义了两个语句。
    // 首先,我们计算出 isEven 变量,表示数字是否为偶数。
    // 然后,我们返回一个布尔值,判断数字既是偶数又大于 2。
    // 通过使用匿名函数,我们可以在函数体中包含多个语句,更好地处理复杂的过滤逻辑。
    // fun(num) : Boolean显式指定了返回类型Boolean;多条语句情况下匿名函数语义更清晰
    val filteredNumbersAnonymous = numbers.filter (fun(num) : Boolean {
        val isEven = num % 2 == 0
        return isEven && num > 2
    })

    // Lambda 表达式的函数体与匿名函数相同,包含了多个语句。
    // Lambda 表达式的语法更为简洁,但在这种情况下,匿名函数的定义也相对清晰。
    val filteredNumbersLambda = numbers.filter { num ->
        val isEven = num % 2 == 0
        isEven && num >2
    }

    println("Filtered numbers: $filteredNumbersAnonymous") //输出:Filtered numbers: [4]
    println("Filtered numbers: $filteredNumbersLambda") //输出:Filtered numbers: [4]
}

内联函数

当一个函数被声明为 inline 时,编译器会在调用该函数的地方将函数的实际代码复制到调用处,而不是通过函数调用的方式进行执行。

优点:

  • 减少函数调用的开销:函数调用会涉及到栈帧的创建、参数传递等操作,而通过函数内联可以避免这些开销,提高代码的执行效率。

缺点:

  • 不宜过度使用,可能会导致代码膨胀:内联函数的复制会增加代码的大小,因此在频繁调用的小函数上使用 inline 可能会导致代码膨胀,影响包的大小和性能。

  • 不可内联的情况:某些情况下,编译器无法进行函数内联,例如递归函数、函数体包含函数表达式、函数参数被函数引用等。

代码举例:

Kotlin 复制代码
//我们定义了一个名为 measureTime 的 inline 函数。该函数接受一个函数类型的参数 execute
inline fun measureTime(execute: () -> Unit) {
    //函数体内部,我们记录了开始时间,执行了传递的代码块 block,然后记录了结束时间,并打印出代码块的执行时间。
    val startTime = System.currentTimeMillis()
    execute()
    val endTime = System.currentTimeMillis()
    println("Execution time: ${endTime - startTime} milliseconds")
    //输出:Execution time: 1005 milliseconds
}

fun main() {
    //编译时会将函数调用处的代码块直接替换到函数体内部,而不会创建额外的函数调用。这样可以减少函数调用的开销,并且在一些情况下可以提高性能。
    measureTime {
        // 执行一些耗时的操作
        Thread.sleep(1000)
    }
}
相关推荐
长亭外的少年14 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
JIAY_WX14 小时前
kotlin
开发语言·kotlin
麦田里的守望者江21 小时前
KMP 中的 expect 和 actual 声明
android·ios·kotlin
菠菠萝宝1 天前
【YOLOv8】安卓端部署-1-项目介绍
android·java·c++·yolo·目标检测·目标跟踪·kotlin
恋猫de小郭1 天前
Kotlin Multiplatform 未来将采用基于 JetBrains Fleet 定制的独立 IDE
开发语言·ide·kotlin
枫__________2 天前
kotlin 协程 job的cancel与cancelAndJoin区别
android·开发语言·kotlin
鸠摩智首席音效师2 天前
如何在 Ubuntu 上配置 Kotlin 应用环境 ?
linux·ubuntu·kotlin
jikuaidi6yuan4 天前
Java与Kotlin在鸿蒙中的地位
java·kotlin·harmonyos
liulanba4 天前
Kotlin的data class
前端·微信·kotlin
小白学大数据4 天前
使用OkHttp进行HTTPS请求的Kotlin实现
爬虫·python·okhttp·https·kotlin