简介

Kotlin是一种现代、简洁且功能强大的编程语言,特别适合Android开发。本文将从基础语法开始,逐步掌握Kotlin的核心特性。

目录

  1. 变量
  2. 基本类型
  3. 集合
  4. 控制流程
  5. 函数
  6. 空安全

学习语法都将从Hello World开始,我们先分析一下在Kotlin中的Hello World :

fun main() {
println("Hello World") // 输出为 Hello World
}

在上述代码实例中我们可以发现,在Kotlin语法中:

  • fun 用于声明函数;
  • 函数 main() 是程序的起始位置;
  • 函数主体是在 {} 中;
  • println() 和 print() 函数会将其参数打印到标准输出。

函数是一组执行特定任务的指令。创建函数后,可以在需要执行改任务时使用它,而无需重新编写指令。在后续 函数 中在着重讨论。

变量

所有程序都需要能够存储数据,而变量就可以帮助做到这一点。在Kotlin中,声明变量分为:

  • 只读变量 val : 一旦赋予只读变量,就无法更改该变量的值。

    • 必须在声明时或构造函数中初始化;
    • 初始化后不能重新赋值;
    • 类似Java中的 final 变量。
  • 可变变量 var : 可以在初始化对变量进行重新赋值。
    • 必须在声明时或构造函数中初始化;
    • 初始化后可以重新赋值;
    • 类似Java中的普通变量。
fun main() {
val a = 5 // 给只读变量初始时赋值 5
println(a) // 输出 a 为 5
a = 4 // 编译器报错 var b = 10 // 给可变变量初始时赋值为 10
println(b) // 输出 b 为 10
b = 20 // 重新给可变变量赋值为 20
println(b) // 输出 b 为 20
}

虽然 val 声明的变量是不可变的,但是如果它是一个对象引用,对象的内部状态仍可以发送改变 例如

fun main() {
val list = mutableListOf(1, 2, 3)
println(list) // 输出为 [1, 2, 3]
list.add(4)
println(list) // 输出为 [1, 2, 3, 4]
list = mutableListOf(5, 6, 7) // 编译器报错,因为是只读变量,不能被重新赋值 var list2 = mutableListOf(1, 2, 3)
println(list2) // 输出为 [1, 2, 3]
list2.add(4)
println(list2) // 输出为 [1, 2, 3, 4]
list2 = mutableListOf(5, 6, 7) // 因为是可变变量, 所以可以重新赋值
println(list2) // 输出为 [5, 6, 7]
}

在编写代码中,优先使用 val ,除非确实需要改变变量的值,并且使用 val 可以提高代码的可读性和安全性,避免意外的变量修改。通过合理的使用 val 和 var,可以更好的管理变量的可变性,提升代码的健壮性。

基本类型

在Kotlin中的每个变量和数据结构都有类型。类型很重要,因为它们告诉编译器可以对该变量或者数据结构执行什么操作。换句话说,它具有哪些函数和属性。

在Kotlin中,一切皆为对象,因为我们可以调用任何变量的成员函数和属性。虽然某些类型在运行时具有优化的内部表示形式(如数字、字符、布尔值等),但它们对开发者来说看起来和表现起来都香常规类。

接下来介绍一下Kotlin中使用的基本类型:

数字(Numbers)

  • 整数类型

    Kotlin提供了一组表示数字的内置类型,对于整数,有四种类型,它们的大小和值范围不同:

    类型 位数 最小值 最大值
    Byte 8 -128 127
    Short 16 -32768 32767
    Int 32 -2,147,483,648 (-2^31) 2,147,483,647 (2^31 - 1)
    Long 64 -9,223,372,036,854,775,808 (-2^63) 9,223,372,036,854,775,807 (2^63 - 1)
    fun main() {
    /**
    * 当你初始化一个没有显式类型规范的变量时,编译器会自动推断出具有最小范围的类型,该范围足以表示从Int开始的值。
    * 如果它没有超过Int的范围,则类型为Int。
    * 如果它超过了该范围,则类型为Long。
    * 要明确指定Long值,请在值后附加后缀L。
    * 要使用Byte或Short类型,请在声明中明确指定。
    * 显式类型规范触发编译器检查值是否不超过指定类型的范围。
    * */
    val one = 1 // Int
    val threeBillion = 3000000000 // Long
    val oneLong = 1L // Long
    val oneByte: Byte = 1
    }
  • 浮点类型

    对于实数,Kotlin提供了符合IEEE 754标准的浮点类型Float和Double。Float反映IEEE 754单精度,而Double反映双精度。

    这些类型的大小不同,为不同精度的浮点数提供存储空间:

    类型 位数 有效位 指数位 小数位
    Flot 32 23 8 6-7
    Double 64 53 11 15-16
    fun main() {
    
        /**
    * 要明确指定值的浮点类型,请添加后缀f或f。如果以这种方式提供的值包含7个以上的十进制数字,则四舍五入:
    * */
    val e = 2.7182818284 // Double
    val eFloat = 2.7182818284f // Float, 实际值为2.7182817 /**
    * 我们只能用有小数部分的数字初始化Double和Float变量。用句点(.)分隔小数部分和整数部分
    *
    * 对于用分数初始化的变量,编译器推断Double类型:
    * */ val pi = 3.14 // Double val one: Double = 1 // 编译器会报错:初始化器类型不匹配 val oneDouble = 1.0 // Double /**
    * 与其他一些语言不同,Kotlin中没有数字的隐式加宽转换。
    * 例如,具有Double参数的函数只能在Double值上调用,而不能在Float、Int或其他数值上调用:
    * */ val x = 1.0
    val xInt = 1
    val xFloat = 1.0f printDouble(x) printDouble(xInt) // 编译器会报错:参数类型不匹配 printDouble(xFloat) // 编译器会报错:参数类型不匹配
    } private fun printDouble(x: Double) {
    println(x)
    }

无符号对应项(Unsigned Counterparts)

  • 无符号整数类型

    除了整数类型之外,Kotlin还为无符号整数提供了以下类型:

    类型 位数 最小值 最大值
    UByte 8 0 255
    UShort 16 0 65,535
    UInt 32 0 4,294,967,295 (2^32 - 1)
    ULong 64 0 18,446,744,073,709,551,615 (2^64 - 1)

    无符号类型支持有符号类型对应的大多数操作。

    无符号数字被实现为具有单个存储属性的内联类,该属性包含相同宽度的相应有符号对应类型。如果要在无符号整数类型和有符号整数类型之间进行转换,请确保更新代码,以便任何函数调用和操作都支持新类型。

  • 无符号数组和范围

    与基元相同,每个无符号类型都有一个对应的类型,表示该类型的数组:

    • UByteArray:一个无符号字节数组。
    • UShortArray:一个无符号短裤数组。
    • UIntArray:一个无符号整数数组。
    • ULongArray:一个无符号长数组。

布尔值(Booleans)

布尔类型表示可以有两个值的布尔对象:true和false。

在JVM上,存储为原始布尔类型的布尔值通常使用8位。

布尔值上的内置操作包括:

|| – 逻辑 或

&& – 逻辑 与

!– 非

fun main () {
val myTrue: Boolean = true
val myFalse: Boolean = false
val boolNull: Boolean? = null println(myTrue || myFalse) // true
println(myTrue && myFalse) // false
println(!myTrue) // false
println(boolNull) // null /**
* ||和&&运算符工作迟缓,这意味着:
*
* 如果第一个操作数为真,则||运算符不会计算第二个操作数。
*
* 如果第一个操作数为false,则&&运算符不会计算第二个操作数。
*
* 在JVM上,对布尔对象的可空引用被打包在Java类中,就像数字一样。
* */
}

字符和字符串 (Char and String)

  • 字符由Char类型表示。字符文字放在单引号中:“1”。

    在JVM上,存储为原始类型char的字符表示16位Unicode字符。

    特殊字符以转义反斜杠\开头。支持以下转义序列:

    • \t – tab(空格)
    • \b – backspace(退格)
    • \n – new line (LF)(新线)
    • \r – carriage return (CR)(回车)
    • \' – single quotation mark(单引号)
    • \" – double quotation mark(双引号)
    • \\ – backslash(反斜杠)
    • \$ – dollar sign(美元符号)
  • Kotlin中的字符串类型表示为String

    在 JVM 上,StringUTF-16 编码类型的对象每个字符大约使用 2 个字节。

    fun main () {
    /**
    * 通常,字符串值是双引号(“)中的字符序列:
    * */
    val str = "abcd" /**
    * 字符串的元素是可以通过索引操作访问的字符:s[i]。我们可以使用for循环迭代这些字符:
    * */
    for (c in str) {
    println(c)
    } /**
    * 字符串是不可变的。初始化字符串后,就不能更改其值或为其分配新值。所有转换字符串的操作都会在新的string对象中返回结果,而原始字符串保持不变:
    * */
    println(str) // 输出为 abcd println(str.uppercase()) // 输出为 ABCD println(str) // 依旧输出为 abcd /**
    * 要连接字符串,请使用+运算符。这也适用于将字符串与其他类型的值连接起来,只要表达式中的第一个元素是字符串:
    * */
    val s = "abc" + 1
    println(s + "def") // 输出为: abc1def // 在大多数情况下,使用字符串模板或多行字符串比字符串连接更可取。 }

    Kotlin 中有两种类型的字符串文字:

    • 转义字符串

      转义字符串可以包含转义字符,例如

      fun main () {
      // 转义以常规方式进行,使用反斜杠 ( \)。
      val s = "Hello, World!\n"
      }
    • 多行字符串

      多行字符串可以包含换行符和任意文本。它由三重引号 ( ) 分隔""",不包含转义符,可以包含换行符和任何其他字符:

      fun main () {
      val text = """
      for (c in "foo")
      print(c)
      """
      }

      要从多行字符串中删除前导空格,请使用以下trimMargin()函数:

      fun main () {
      val text = """
      |Tell me and I forget.
      |Teach me and I remember.
      |Involve me and I learn.
      |(Benjamin Franklin)
      """.trimMargin()
      }

      默认情况下,使用管道符号|作为边距前缀,但我们可以选择其他字符并将其作为参数传递,例如trimMargin(">")

  • 字符串模版

    fun main () {
    /**
    * 字符串文字可能包含模板表达式- 被求值的代码片段,其结果被连接成字符串。
    * 处理模板表达式时,Kotlin 会自动调用.toString()表达式结果上的函数将其转换为字符串。模板表达式以美元符号 ( $) 开头,由以下变量名组成:
    * */
    val i = 10
    println("i = $i") // 输出为 i = 10 val letters = listOf("a","b","c","d","e")
    println("Letters: $letters") // 输出为 Letters: [a, b, c, d, e] /**
    * 或花括号中的表达式:
    * */
    val s = "abc"
    println("$s.length is ${s.length}") // 输出为 abc.length is 3 /**
    * 我们可以在多行和转义字符串中使用模板。
    * 但是,多行字符串不支持反斜杠转义。要在标识符开头允许的任何符号之前在多行字符串中插入美元符号$,请使用以下语法:
    * */ val price = """
    ${'$'}_9.99
    """.trimIndent()
    println(price) }
  • 字符串格式

    要根据我们的特定要求格式化字符串,请使用string.format()函数。

    函数接受一个格式字符串和一个或多个参数。格式字符串包含给定参数的一个占位符(由%表示),后跟格式说明符。

    格式说明符是相应参数的格式化指令,由标志、宽度、精度和转换类型组成。

    总的来说,格式说明符决定了输出的格式。

    常见的格式说明符包括%d表示整数,%f表示浮点数,%s表示字符串。我们还可以使用argument_index$语法在不同格式的格式字符串中多次引用同一个参数。

    fun main() {
    // 格式化一个整数,添加前导零以达到七个字符的长度
    val integerNumber = String.format("%07d", 31416)
    println(integerNumber) // 输出为 0031416 // 格式化浮点数以显示+号和四位小数
    val floatNumber = String.format("%+.4f", 3.141592)
    println(floatNumber) // 输出为 +3.1416 // 将两个字符串格式化为大写,每个字符串取一个占位符
    val helloString = String.format("%S %S", "hello", "world")
    println(helloString) // 输出为 HELLO WORLD // 格式化一个负数并将其括在括号中,然后使用`argument_index$`以不同的格式(不带括号)重复相同的数字。
    val negativeNumberInParentheses = String.format("%(d means %1\$d", -31416)
    println(negativeNumberInParentheses) //输出为 (31416) means -31416 }

数组(Arrays)

数组是一种数据结构,它包含固定数量的相同类型或其子类型的值。Kotlin中最常见的数组类型是对象类型数组,由array类表示。

如果在对象类型数组中使用基元,这会对性能产生影响,因为基元被打包成对象。为了避免装箱开销,请改用基本类型数组。

  • 创建数组

    要在Kotlin中创建数组,我们可以使用:

    • 函数,例如 arrayOf()、arrayOfNulls()、emptyArray()
    • 构造函数Array
    fun main() {
    // 创建一个数组 [1, 2, 3]
    val sampleArray = arrayOf(1, 2, 3)
    println(sampleArray.joinToString()) // 输出为 1, 2, 3 // 创建一个数组 [null, null, null]
    val nullArray: Array<Int?> = arrayOfNulls(3)
    println(nullArray.joinToString()) // 输出为 null, null ,null // 创建一个用零初始化的 Array<Int> [0, 0, 0]
    val initArray = Array<Int>(3) { 0 }
    println(initArray.joinToString()) // 输出为 0, 0, 0 // 创建一个 Array<String> 其值为 ["0", "1", "4", "9", "16"]
    val asc = Array(5) { i -> (i * i).toString() }
    asc.forEach { print(it) } // 014916 println() /**
    * 嵌套数组
    * 数组可以相互嵌套以创建多维数组
    * */
    // 创建二维数组
    val twoDArray = Array(2) { Array<Int>(2) { 0 } }
    println(twoDArray.contentDeepToString()) // 输出为 [[0, 0], [0, 0]] // 创建三维数组
    val threeDArray = Array(3) { Array(3) { Array<Int>(3) { 0 } } }
    println(threeDArray.contentDeepToString()) // 输出为 [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]]] }

    嵌套数组不必是相同的类型或大小。

  • 访问和修改元素

    数组始终是可变的。要访问和修改数组中的元素,可以使用索引访问运算符[]:

    fun main() {
    val simpleArray = arrayOf(1, 2, 3)
    val twoDArray = Array(2) { Array<Int>(2) { 0 } } println(simpleArray[0].toString()) // 输出为 11
    println(twoDArray[0][0].toString()) // 输出为 0 // 访问元素并修改它
    simpleArray[0] = 10
    twoDArray[0][0] = 2 println(simpleArray[0].toString()) // 输出为 10
    println(twoDArray[0][0].toString()) // 输出为 2
    }

    Kotlin中的数组是不变的。这意味着Kotlin不允许我们将Array分配给Array,以防止可能的运行时故障。相反,我们可以使用Array。

  • 使用数组

    在Kotlin中,我们可以通过使用数组将可变数量的参数传递给函数或对数组本身执行操作来使用数组。例如,比较数组、转换其内容或将其转换为集合。

    将可变数量的参数传递给函数

    在Kotlin中,你可以通过vararg参数向函数传递可变数量的参数。当我们事先不知道参数的数量时,这很有用,比如在格式化消息或创建SQL查询时。

    要将包含可变数量参数的数组传递给函数,请使用spread运算符(*)。spread运算符将数组的每个元素作为单独的参数传递给我们选择的函数:

    fun main() {
    val lettersArray = arrayOf("c", "d")
    printAllStrings("a", "b", *lettersArray) // 输出为 abcd
    } private fun printAllStrings(vararg strings: String) {
    for (string in strings) {
    print(string)
    }
    }
  • 比较数组

    要比较两个数组是否具有相同顺序的相同元素,请使用.contentEquals()和.contentDeepEquals()函数:

    fun main() {
    val simpleArray = arrayOf(1, 2, 3)
    val anotherArray = arrayOf(1, 2, 3) // 比较数组的内容
    println(simpleArray.contentEquals(anotherArray)) // 结果为 true // 使用中缀表示法,比较元素后数组的内容
    simpleArray[0] = 10
    println(simpleArray contentEquals anotherArray) // 结果为 false
    }

    不要使用等式(==)和不等式(!=)运算符来比较数组的内容。这些运算符检查分配的变量是否指向同一对象。

  • 原始类型数组

    如果将Array类与基元值一起使用,这些值将被装箱到对象中。作为替代方案,我们可以使用基元类型数组,这允许我们在数组中存储基元,而不会产生装箱开销的副作用:

    原始类型数组 Java中对应代码
    BooleanArray boolean[]
    ByteArray byte[]
    CharArray char[]
    DoubleArray double[]
    FloatArray float[]
    IntArray int[]
    LongArray long[]
    ShortArray short[]

集合

Kotlin标准库提供了一套全面的工具来管理集合——一组数量可变(可能为零)的项目,这些项目对正在解决的问题很重要,并且通常会被操作。

一个集合通常包含许多相同类型(及其子类型)的对象。集合中的对象称为元素或项。例如,一个系的所有学生组成一个集合,可用于计算他们的平均年龄。

以下集合类型与Koltin相关:

  • List

    List是一个有序的集合,可以通过索引(反映其位置的整数)访问元素。元素在列表中可以出现多次。
  • Set

    Set是一组独特元素的集合。它反映了集合的数学抽象:一组没有重复的对象。一般来说,集合元素的顺序没有意义。
  • Map

    Map(或字典)是一组键值对。键是唯一的,每个键都映射到了一个值。这些值可以是重复的。

Kotlin运行我们独立与存储在集合中的对象的确切类型来操纵集合。就是说,我们可以像处理Int或自定义类一样,将其添加到对应列表中。因此,Kotlin标准库提供了通用接口、类和函数,用于创建、填充和管理任何类型的集合。

注:数组不是集合类型

集合类型

Koltin标准库提供了基本集合类型的实现:list、set、map。一对接口代表每种集合类型:

一个只读接口,提供访问集合元素的操作。例如 listOf()、mapOf()、setOf()。

一个可变接口,通过写操作扩展相应的只读接口:添加、删除和更新元素。例如:mutableListOf()、mutableMapOf()、mutableSetOf()。

值得注意的是,可变集合不必分配给var。即使将可变集合分配给val,使用可变集合的写入操作仍然是可能的。将可变集合指定给val的好处是可以保护对可变集合的引用免受修改。随着时间的推移,随着代码的增长和变得更加复杂,防止对引用的无意修改变得更加重要。尽可能多地使用val,以获得更安全、更健壮的代码。如果尝试重新分配val集合,则会出现编译错误:

fun main() {
val numbers = mutableListOf("One", "Two", "Three", "Four")
println(numbers) // 输出 [One, Two, Three, Four]
numbers.add("Five")
println(numbers) // 输出 [One, Two, Three, Four, Five]
numbers = mutableListOf("Six", "Seven") // 编译报错 Val cannot be reassigned
}

只读集合类型是协变的。这意味着,如果Rectangle类继承自Shape,则可以在需要List的任何地方使用List。换句话说,集合类型与元素类型具有相同的子类型关系。映射在值类型上是协变的,但在键类型上不是协变的。

反过来,可变集合不是协变的;否则,这将导致运行时失败。如果MutableList是MutableList的子类型,则可以将其他Shape继承器(例如Circle)插入其中,从而违反其Rectangle类型参数。

下面是Kotlin集合接口的示意图:

List

List按指定顺序存储元素,并提供对它们的索引访问。索引从零开始,即第一个元素的索引,然后转到lastIndex,即(list.size-1)。

fun main() {
val numbers = mutableListOf("One", "Two", "Three", "Four")
println("数组大小: ${numbers.size}") // 输出 数组大小: 4
println("第三个元素: ${numbers[2]}") // 输出 第三个元素: Three
println("第四个元素: ${numbers[3]}") // 输出 第四个元素: Four
println("查找元素‘Two’的位置: ${numbers.indexOf("Two")}") // 输出 查找元素‘Two’的位置: 1
}

列表元素(包括空值)可以重复:列表可以包含任意数量的相等对象或单个对象的出现。如果两个列表在相同位置具有相同大小和结构上相同的元素,则认为它们是相等的。

fun main() {
val bob = Person("Bob", 18)
val people = listOf(Person("Adam", 20), bob, bob)
val people2 = listOf(Person("Adam", 20), Person("Bob", 18), bob)
println(people == people2) // 在位置0和1和2的地方,people和people2的数据是相同的,所以输出为 true
bob.age = 20
println(people == people2) // 在位置0的地方,people和people2的数据是相同的,但1和2位置的数据是不相同的,所以输出为 false }
data class Person(
var name: String,
var age: Int
)

MutableList是一个具有特定于列表的写入操作的列表,例如,在特定位置添加或删除元素。

fun main() {
val numbers = mutableListOf(1, 2, 3, 4)
println(numbers) // 输出为 [1, 2, 3, 4]
numbers.add(5)
println(numbers) // 输出为 [1, 2, 3, 4, 5]
numbers.removeAt(1)
println(numbers) // 输出为 [1, 3, 4, 5]
numbers[0] = 0
println(numbers) // 输出为 [0, 3, 4, 5]
numbers.shuffle()
println(numbers) // 输出为 [5, 0, 4, 3]
}

如上述所见,在某些方面,列表与数组非常相似。然而,有一个重要的区别:数组的大小是在初始化时定义的,永远不会改变;反过来,列表没有预定义的大小;列表的大小可以因写入操作而改变:添加、更新或删除元素。

在Kotlin中,MutableList的默认实现是ArrayList,我们可以将其视为可调整大小的数组。

Set

Set存储唯一元素;它们的顺序通常没有定义。null元素也是唯一的:一个Set只能包含一个null。如果两个集合具有相同的大小,则它们是相等的,并且对于一个集合的每个元素,另一个集合中都有一个相等的元素。

fun main() {
val numbers = setOf(1, 2, 3, 4)
println("numbers 数量为: ${numbers.size}") // 输出为 numbers 数量为: 4
if (numbers.contains(1)) {
println("1 在该集合中") // 输出为 1 在该集合中
}
val numberBackwards = setOf(4, 3, 2, 1)
println("两组是相等的: ${numbers == numberBackwards}") // 输出为 两组是相等的: true
}

MutableSet是一个来自MutableCollection的具有写入操作的集合。

MutableSet的默认实现——LinkedHashSet——保留了元素插入的顺序。因此,依赖于顺序的函数,如first()或last(),在这些集合上返回可预测的结果。

fun main() {
val numbers = mutableSetOf(1, 2, 3, 4) // LinkedHashSet是默认实现
val numbersBackwards = mutableSetOf(4, 3, 2, 1) println(numbers.first() == numbersBackwards.first()) // 输出为 false
println(numbers.first() == numbersBackwards.last()) // 输出为 true
}

另一种实现——HashSet——对元素顺序一无所知,因此在其上调用此类函数会返回不可预测的结果。然而,HashSet需要更少的内存来存储相同数量的元素。

Map

Map<K,V>不是集合接口的继承者;然而,它也是Kotlin集合类型。Map存储键值对(或条目);键是唯一的,但不同的键可以与相等的值配对。Map界面提供了特定的功能,例如按键访问值、搜索键和值等。

fun main() {
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1) println("所有keys: ${numbersMap.keys}") // 输出为 所有keys: [key1, key2, key3, key4]
println("所有values: ${numbersMap.values}") // 输出为 所有values: [1, 2, 3, 1] if ("key2" in numbersMap) {
println("key 为 key2的值为: ${numbersMap["key2"]}") // 输出为 key 为 key2的值为: 2
} if (1 in numbersMap.values) {
println("值为 1 在集合中存在") // 输出为 值为 1 在集合中存在
} if (numbersMap.containsValue(1)) {
println("值为 1 在集合中存在") // 输出为 值为 1 在集合中存在
}
}

无论配对顺序如何,包含相等配对的两个映射都是相等的。

fun main() {
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3) println("两个集合是否相同: ${numbersMap == anotherMap}") // 输出为 true
}

MutableMap是一个具有映射写入操作的映射,例如,您可以添加一个新的键值对或更新与给定键关联的值。

fun main() {
val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap["three"] = 3
numbersMap["one"] = 11 println(numbersMap) // 输出为 {one=11, two=2, three=3}
}

MutableMap的默认实现——LinkedHashMap——在迭代映射时保留了元素插入的顺序。反过来,另一种实现——HashMap——对元素顺序一无所知。

ArrayDeque

ArrayDeque是一个双端队列的实现,它允许您在队列的开头或结尾添加或删除元素。因此,ArrayDeque在Kotlin中同时扮演了Stack和Queue数据结构的角色。在幕后,ArrayDeque是使用一个可调整大小的数组来实现的,该数组在需要时会自动调整大小:

fun main() {
val deque = ArrayDeque(listOf(1, 2, 3))
println(deque) // 输出为 [1, 2, 3] deque.addFirst(0)
println(deque) // 输出为 [0, 1, 2, 3] deque.addLast(4)
println(deque) // 输出为 [0, 1, 2, 3, 4] println(deque.first()) // 输出为 0 println(deque.last()) // 输出为 4 deque.removeFirst()
println(deque) // 输出为 [1, 2, 3, 4] deque.removeLast()
println(deque) // 输出为 [1, 2, 3] }

构建集合

从元素构建

创建集合的最常见方法是使用标准库函数listOf(), setOf(), mutableListOf(), mutableSetOf()。如果提供以逗号分隔的集合元素列表作为参数,则编译器会自动检测元素类型。创建空集合时,请明确指定类型。

val numbersSet = setOf("one", "two", "three", "four")
val emptySet = mutableSetOf<String>()

同样适用于具有函数mapOf()和的映射mutableMapOf()。映射的键和值作为Pair对象传递(通常使用中to缀函数创建)。

val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)

请注意,该to符号会创建一个短暂的Pair对象,因此建议仅在性能不重要的情况下使用它。为避免过多的内存使用,请使用其他方法。例如,我们可以创建一个可变映射并使用写入操作填充它。该apply()函数可以帮助保持此处的初始化流畅。

val numbersMap = mutableMapOf<String, String>().apply { this["one"] = "1"; this["two"] = "2" }
使用集合构建函数创建

创建集合的另一种方法是调用构建器函数——buildList()、buildSet()或buildMap()。它们创建了一个相应类型的新的可变集合,使用写操作填充它,并返回一个具有相同元素的只读集合:

fun main() {
val map = buildMap { // 这是MutableMap<String,Int>,键和值的类型从下面的`put()`调用中推断出来
put("a", 1)
put("b", 0)
put("c", 4)
} println(map) // 输出为 {a=1, b=0, c=4} }
空集合

还有一些函数用于创建不包含任何元素的集合:emptyList()emptySet()emptyMap()。创建空集合时,应指定集合将包含的元素类型。

val empty = emptyList<String>()
列表的初始化函数

对于列表,有一个类似构造函数的函数,它采用列表大小和初始化函数,该函数根据其索引定义元素值。

fun main() {
val doubled = List(3, { it * 2 }) // 或MutableList(如果以后要更改其内容)
println(doubled) // 输出为 [0, 2, 4]
}
具体类型构造函数

要创建具体的类型集合,如ArrayList或LinkedList,您可以使用这些类型的可用构造函数。Set和Map的实现也有类似的构造函数。

val linkedList = LinkedList<String>(listOf("one", "two", "three"))
val presizedSet = HashSet<Int>(32)
集合的复制

要创建与现有集合具有相同元素的集合,可以使用复制功能。标准库中的集合复制函数创建引用相同元素的浅复制集合。因此,对集合元素所做的更改会反映在其所有副本中。

集合复制函数,如toList()、toMutableList()和toSet()等,在特定时刻创建集合的快照。它们的结果是相同元素的新集合。如果在原始集合中添加或删除元素,则不会影响副本。副本也可以独立于来源进行更改。

fun main() {
val alice = Person("Alice")
val sourceList = mutableListOf(alice, Person("Bob"))
val copyList = sourceList.toList() sourceList.add(Person("Charles"))
alice.name = "Alicia" // 输出为 源集合中第一个Item的值为: Alicia, 复制集合中第一个Item的值为: Alicia
println("源集合中第一个Item的值为: ${sourceList[0].name}, 复制集合中第一个Item的值为: ${copyList[0].name}") // 输出为 源集合的大小: 3, 复制集合的大小 2
println("源集合的大小: ${sourceList.size}, 复制集合的大小 ${copyList.size} ") }
class Person(var name: String)

这些函数也可用于将集合转换为其他类型,例如,从列表构建集合,反之亦然。

fun main() {
val sourceList = mutableListOf(1, 2, 3)
val copySet = sourceList.toMutableSet()
copySet.add(3)
copySet.add(4)
println(copySet) // 输出为 [1, 2, 3, 4]
}

或者,可以创建对同一集合实例的新引用。当您使用现有集合初始化集合变量时,会创建新的引用。因此,当通过引用更改集合实例时,这些更改会反映在其所有引用中。

fun main() {
val sourceList = mutableListOf(1, 2, 3)
val referenceList = sourceList
println("Source size: ${sourceList.size}") // 输出为 3
referenceList.add(4)
println("Source size: ${sourceList.size}") // 输出为 4
}

集合初始化可用于限制可变性。例如,如果创建了一个对MutableList的List引用,如果试图通过此引用修改集合,编译器将产生错误。

fun main() {
val sourceList = mutableListOf(1, 2, 3)
val referenceList: List<Int> = sourceList
// referenceList.add(4) // 编译器报错
sourceList.add(4)
println(referenceList) // 输出为 [1, 2, 3, 4]
}
调用其他集合上的函数

可以通过对其他集合进行各种操作来创建集合。例如,过滤列表会创建一个与过滤器匹配的新元素列表:

fun main() {
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3) // 输出为 [three, four]
}

映射根据转换的结果生成一个列表:

fun main() {
val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 }) // 输出为 [3, 6, 9]
println(numbers.mapIndexed {idx, value -> value * idx}) // 输出为 [0, 2, 6]
}

迭代器

对于遍历集合元素,Kotlin标准库支持常用的迭代器机制,迭代器是按顺序访问元素而不暴露集合底层结构的对象。当我们需要逐一处理集合的所有元素时,迭代器非常有用,例如打印值或对其进行类似的更新。

通过调用iterator()函数,可以获得Iterable接口的继承者的迭代器,包括Set和List。

一旦你获得一个迭代器,它就会指向集合的第一个元素;调用next()函数将返回此元素,并将迭代器位置移动到以下元素(如果存在)。

一旦迭代器通过最后一个元素,它就不能再用于检索元素;它也不能重置到任何先前的位置。要再次迭代集合,请创建一个新的迭代器。

fun main() {
val numbers = listOf("one", "two", "three", "four")
val numbersIterator = numbers.iterator()
while (numbersIterator.hasNext()) {
println(numbersIterator.next())
// 输出
// one
// two
// three
// four
}
}

遍历Iterable集合的另一种方法是众所周知的for循环。在集合上使用for时,可以隐式获取迭代器。因此,以下代码与上述示例等效:

fun main() {
val numbers = listOf("one", "two", "three", "four")
for (number in numbers) {
println(number)
// 输出
// one
// two
// three
// four
}
}

最后,有一个有用的forEach()函数,它允许您自动迭代集合并为每个元素执行给定的代码。所以,同样的例子看起来像这样:

fun main() {
val numbers = listOf("one", "two", "three", "four")
numbers.forEach {
println(it)
// 输出
// one
// two
// three
// four
}
}
列表迭代器

对于列表,有一个特殊的迭代器实现:ListIterator。它支持在两个方向上迭代列表:向前和向后。

反向迭代是通过函数hasPrevious()和previous()实现的。此外,ListIterator通过函数nextIndex()和previousIndex()提供有关元素索引的信息。

fun main() {
val numbers = listOf("one", "two", "three", "four")
val listIterator = numbers.listIterator()
while (listIterator.hasNext()) listIterator.next()
println("向后迭代:")
while (listIterator.hasPrevious()) {
print("index: ${listIterator.previousIndex()}")
println(", 值为: ${listIterator.previous()}")
// 输出为
// index: 3, 值为: four
// index: 2, 值为: three
// index: 1, 值为: two
// index: 0, 值为: one
}
}

具有双向迭代的能力意味着ListIterator在到达最后一个元素后仍然可以使用。

可变迭代器

对于迭代可变集合,有一个MutableIterator,它使用元素删除函数remove()扩展了Iterator。因此,我们可以在迭代集合时从集合中删除元素。

fun main() {
val numbers = mutableListOf("one", "two", "three", "four")
val mutableIterator = numbers.iterator() mutableIterator.next()
mutableIterator.remove()
println("删除之后: $numbers") // 输出为 删除之后: [two, three, four]
}

除了删除元素外,MutableListIterator还可以在使用add()和set()函数迭代列表时插入和替换元素。

fun main() {
val numbers = mutableListOf("one", "four", "four")
val mutableListIterator = numbers.listIterator() mutableListIterator.next()
mutableListIterator.add("two")
println(numbers) // 输出为 [one, two, four, four] mutableListIterator.next()
mutableListIterator.set("three")
println(numbers) // 输出为 [one, two, three, four] }

筛选

筛选是收集集合中最受欢迎的任务之一。在Kotlin中,过滤条件由谓词定义——lambda函数接受一个集合元素并返回一个布尔值:true表示给定元素与谓词匹配,false表示相反。

标准库包含一组扩展函数,允许我们在一次调用中筛选集合。这些函数保持原始集合不变,因此它们既可用于可变集合,也可用于只读集合。要操作筛选结果,我们应该将其分配给变量或在筛选后链接函数。

按谓词筛选

基本的过滤函数是filter()。当使用谓词调用filter()时,它返回与之匹配的集合元素。对于List和Set,得到的集合都是List,对于Map,它也是Map。

fun main() {
val numbers = listOf("One", "Two", "Three", "Four", "Five")
val longerThan3 = numbers.filter { it.length > 3 }
println("字符长度大于3的有: $longerThan3") // 输出为 字符长度大于3的有: [Three, Four, Five] val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key11" to 11)
val filteredMap = numbersMap.filter { (key, value) -> key.endsWith("1") && value > 10}
println("Key值以'1'为结尾,并且获取到value值大于10的有: $filteredMap") // 输出为 Key值以'1'为结尾,并且获取到value值大于10的有: {key11=11}
}

filter()中的谓词只能检查元素的值。如果想在过滤器中使用元素位置,请使用filterIndexed()。它接受一个有两个参数的谓词:元素的索引和值。

要按负数条件筛选集合,请使用filterNot()。它返回谓词为false的元素列表。

fun main() {
val numbers = listOf("One", "Two", "Three", "Four", "Five") val filteredIdx = numbers.filterIndexed { index, s -> (index != 0) && (s.length < 5) }
println("index不等于0并且字符长度小于5的有: $filteredIdx") // 输出为 index不等于0并且字符长度小于5的有: [Two, Four, Five] val filteredNot = numbers.filterNot { it.length <= 3 }
println("字符长度大于3的有: $filteredNot") // 输出为 字符长度大于3的有: [Three, Four, Five]
}

还有一些函数通过过滤给定类型的元素来缩小元素类型:

  • filterIsInstance()返回给定类型的集合元素。在List上被调用时,filterIsInstance()返回一个List,从而允许您对其项调用T类型的函数。

    fun main() {
    val numbers = listOf(null, 1, "two", 3.0, "four") println("所有String元素均大写:")
    numbers.filterIsInstance<String>().forEach {
    println(it.uppercase())
    }
    // 输出为
    // 所有String元素均大写:
    // TWO
    // FOUR
    }
  • filterNotNull()返回所有不可为null的元素。在列表<T?>中被调用,filterNotNull()返回一个List<T:Any>,从而允许您将元素视为不可为null的对象。

    fun main() {
    val numbers = listOf(null, "one", "two", null)
    numbers.filterNotNull().forEach {
    println(it.length) // 长度不适用于可以为null的字符串
    }
    // 输出为
    // 3
    // 3
    }
分割

另一个过滤函数partition()通过谓词过滤集合,并将不匹配的元素保存在单独的列表中。因此,我们有一对列表作为返回值:第一个列表包含与谓词匹配的元素,第二个列表包含原始集合中的所有其他元素。

fun main() {
val numbers = listOf("one", "two", "three", "four")
val (match, rest) = numbers.partition { it.length > 3 } println(match) // 输出为 [three, four]
println(rest) // 输出为 [one, two]
}

分组

Kotlin标准库提供了用于对集合元素进行分组的扩展函数。基本函数groupBy()接受一个lambda函数并返回一个Map。在这个映射中,每个键都是lambda结果,相应的值是返回此结果的元素列表。例如,此函数可用于按字符串的第一个字母对字符串列表进行分组。

我们还可以使用第二个lambda参数(值转换函数)调用groupBy()。在带有两个lambda的groupBy()的结果映射中,keySelector函数生成的键被映射到值转换函数的结果,而不是原始元素。

此示例说明了如何使用groupBy()函数按字符串的第一个字母对字符串进行分组,使用for运算符迭代生成的Map上的组,然后使用keySelector函数将值转换为大写:

fun main() {
val numbers = listOf("one", "two", "three", "four", "five") // 使用groupBy()按字符串的第一个字母对其进行分组
val groupedByFirstLetter = numbers.groupBy { it.first().uppercase() }
println(groupedByFirstLetter) //输出为 {O=[one], T=[two, three], F=[four, five]} // 遍历每个组并打印密钥及其相关值
for ((key, value) in groupedByFirstLetter) {
println("Key: $key, Values: $value")
}
// 输出为
// Key: O, Values: [one]
// Key: T, Values: [two, three]
// Key: F, Values: [four, five] // 按字符串的第一个字母对其进行分组,并将值转换为大写
val groupedAndTransformed = numbers.groupBy(keySelector = { it.first() }, valueTransform = { it.uppercase() })
println(groupedAndTransformed) // 输出为 {o=[ONE], t=[TWO, THREE], f=[FOUR, FIVE]}
}

如果想对元素进行分组,然后一次对所有组应用一个操作,请使用函数groupingBy()。它返回Grouping类型的实例。Grouping实例允许您以一种懒惰的方式对所有组应用操作:这些组实际上是在操作执行之前构建的。

即分组支持以下操作:

eachCount()对每个组中的元素进行计数。

fold()和reduce()将每个组作为单独的集合执行fold和reduce操作,并返回结果。

aggregate()随后对每个组中的所有元素应用给定的操作并返回结果。这是对分组执行任何操作的通用方法。当折叠或缩小不够时,使用它来实现自定义操作。

我们可以在生成的Map上使用for运算符来迭代groupingBy()函数创建的组。这允许我们访问每个键以及与该键关联的元素计数。

以下示例演示了如何使用groupingBy()函数按字符串的第一个字母对字符串进行分组,对每个组中的元素进行计数,然后迭代每个组以打印关键字和元素计数:

fun main() {
val numbers = listOf("one", "two", "three", "four", "five") // 使用groupingBy()按字符串的第一个字母对其进行分组,并对每组中的元素进行计数
val grouped = numbers.groupingBy { it.first() }.eachCount() // 遍历每个组并打印密钥及其相关值
for ((key, count) in grouped) {
println("Key: $key, Count: $count")
// Key: o, Count: 1
// Key: t, Count: 2
// Key: f, Count: 2
}
}

检索

Kotlin标准库包含用于检索集合部分的扩展函数。这些函数提供了多种选择结果集合元素的方法:显式列出它们的位置、指定结果大小等。

  • Slice

    slice()返回具有给定索引的集合元素列表。索引可以作为范围或整数值的集合传递。

    fun main() {
    val numbers = listOf("one", "two", "three", "four", "five", "six")
    println(numbers.slice(1..3)) // 切出位于1、2、3的元素 输出为 [two, three, four]
    println(numbers.slice(0..4 step 2)) // 切出位于从0开始,间隔2的元素 输出为 [one, three, five]
    println(numbers.slice(setOf(3, 5, 0))) // 切出位于3、5、0的元素 输出为 [four, six, one]
    }
  • Take and drop

    要从第一个开始获取指定数量的元素,请使用take()函数。要获取最后一个元素,请使用takeLast()。当使用大于集合大小的数字调用时,这两个函数都会返回整个集合。

    要获取除给定数量的第一个或最后一个元素之外的所有元素,请分别调用drop()和dropLast()函数。

    fun main() {
    val numbers = listOf("one", "two", "three", "four", "five", "six")
    println(numbers.take(3)) // 从头拿取3个元素 输出为 [one, two, three]
    println(numbers.takeLast(3)) // 从后拿取3个元素 输出为 [four, five, six]
    println(numbers.drop(1)) // 丢弃去第一个元素 输出为 [two, three, four, five, six]
    println(numbers.dropLast(4)) // 从后丢弃4个元素 输出为 [one, two]
    }

    我们还可以使用谓词来定义取或放的元素数量。有四个功能与上述功能相似:

    • takeWhile()是带谓词的take():它接受最多但不包括第一个与谓词不匹配的元素。如果第一个集合元素与谓词不匹配,则结果为空。

    • takeLastWhile()类似于takeLast():它从集合末尾获取与谓词匹配的元素范围。范围的第一个元素是与谓词不匹配的最后一个元素旁边的元素。如果最后一个集合元素与谓词不匹配,则结果为空;

    • dropWhile()与具有相同谓词的takeWhile(()相反:它返回从第一个不匹配谓词到末尾的元素。

    • dropLastWhile()与具有相同谓词的takeLastWhile方法相反:它返回从开始到最后一个与谓词不匹配的元素。

    fun main() {
    val numbers = listOf("one", "two", "three", "four", "five", "six", "eight")
    println(numbers.takeWhile { !it.startsWith('f') }) // 从头拿取输出,直到元素含有‘f’ 输出为 [one, two, three]
    println(numbers.takeLastWhile { it != "three" }) // 从后拿取元素,直到元素为‘three’ 输出为 [four, five, six, eight]
    println(numbers.dropWhile { it.length == 3 }) // 从头丢弃元素,直到元素长度大于3 输出为 [three, four, five, six, eight]
    println(numbers.dropLastWhile { it.contains('i') }) // 从后丢弃元素,直到元素不含有i 输出为 [one, two, three, four]
    }
  • chunked

    要将集合分解为给定大小的部分,请使用chunked()函数。chunked()接受一个参数——块的大小——并返回给定大小的列表列表。第一个块从第一个元素开始,包含大小元素,第二个块包含下一个大小元素,以此类推。最后一个块的大小可能较小。

    fun main() {
    val numbers = (0..13).toList()
    println(numbers.chunked(3)) // 分解为三个元素为一组的列表,在返回一个包含子列表的列表 输出为 [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13]]
    }

    我们还可以立即对返回的块应用转换。为此,在调用chunked()时,将转换作为lambda函数提供。lambda参数是集合的一块。当使用转换调用chunked()时,chunks是短生命列表,应该在该lambda中使用。

    fun main() {
    val numbers = (0..13).toList()
    println(numbers.chunked(3)) // 输出为 [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13]]
    println(numbers.chunked(3) { it.sum() }) // 将分割的每组数据累加后,在返回成一个新的列表 输出为 [3, 12, 21, 30, 25]
    }
  • windowed

    我们可以检索给定大小的集合元素的所有可能范围。获取它们的函数称为windowed():它返回一个元素范围列表,如果你通过给定大小的滑动窗口查看集合,你会看到这些元素范围。与chunked()不同,windowed()返回从每个集合元素开始的元素范围(窗口)。所有窗口都作为单个List的元素返回。

    fun main() {
    val numbers = listOf("one", "two", "three", "four", "five")
    println(numbers.windowed(2)) // 输出为 [[one, two], [two, three], [three, four], [four, five]]
    println(numbers.windowed(3)) // 输出为 [[one, two, three], [two, three, four], [three, four, five]]
    println(numbers.windowed(4)) // 输出为 [[one, two, three, four], [two, three, four, five]]
    }

    windowed()通过可选参数提供了更大的灵活性:

    该步骤定义了两个相邻窗口的第一元素之间的距离。默认情况下,该值为1,因此结果包含从所有元素开始的窗口。如果将步长增加到2,则只会收到从奇数元素开始的窗口:第一、第三等。

    partialWindows包括从集合末尾的元素开始的较小大小的窗口。例如,如果您请求三个元素的窗口,则无法为最后两个元素构建它们。在这种情况下启用partialWindows包括另外两个大小为2和1的列表。

    最后,可以立即对返回的范围应用转换。为此,在调用windowed()时,将转换作为lambda函数提供。|

    fun main() {
    val numbers = (1..10).toList()
    println(numbers.windowed(3, step = 2, partialWindows = true)) // 输出为 [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9], [9, 10]]
    println(numbers.windowed(3) { it.sum() }) // 输出为 [6, 9, 12, 15, 18, 21, 24, 27]
    }

    要构建两个元素窗口,有一个单独的函数zipWithNext()。它创建接收器集合的相邻元素对。请注意,zipWithNext()不会将集合分成对;它为除最后一个元素之外的每个元素创建一个Pair,因此它在[1,2,3,4]上的结果是[[1,2],[2,3],[3,4]],而不是[[1,2],[3,4]]。zipWithNext()也可以通过转换函数调用;它应该将接收者集合的两个元素作为参数。

    fun main() {
    val numbers = listOf("one", "two", "three", "four", "five")
    println(numbers.zipWithNext()) // 输出为 [(one, two), (two, three), (three, four), (four, five)]
    println(numbers.zipWithNext() { s1, s2 -> s1.length > s2.length}) // 每个子集合中元素进行比较 输出为 [false, false, true, false]
    }

Kotlin集合提供了一组函数,用于从集合中检索单个元素。此页面上描述的功能适用于列表和集合。

正如列表的定义所说,列表是一个有序的集合。因此,列表中的每个元素都有其可用于引用的位置。除了上述的功能外,列表还提供了更广泛的按索引检索和搜索元素的方法。

反过来讲,set不是一个有序的集合。然而,Kotlin Set按特定顺序存储元素。这些可以是插入顺序(在LinkedHashSet中)、自然排序顺序(在SortedSet中)或其他顺序。一组元素的顺序也可能是未知的。在这种情况下,元素仍然以某种方式排序,因此依赖于元素位置的函数仍然会返回结果。然而,除非调用者知道所使用的Set的具体实现,否则这些结果是不可预测的。

  • 按位置检索

    要在特定位置检索元素,可以使用函数elementAt()。使用整数作为参数调用它,您将在给定位置收到集合元素。第一个元素的位置为0,最后一个元素的大小为-1。

    elementAt()对于不提供索引访问或静态未知提供索引访问的集合很有用。对于List,使用索引访问运算符(get()或[])更习惯。

    fun main() {
    val numbers = linkedSetOf("one", "two", "three", "four", "five")
    // linkedSetOf 创建一个 LinkedHashSet,它是一个有序的集合,元素的顺序与插入顺序一致。
    // 因此,numbers 的顺序是:["one", "two", "three", "four", "five"]。
    println(numbers.elementAt(3)) // 输出 four // sortedSetOf 创建一个 TreeSet,它是一个有序的集合,元素会按照自然顺序(或指定的比较器)排序。
    // 对于字符串,自然顺序是字典序(按字母顺序)。
    // 因此,numbersSortedSet 的顺序是:["a" ,"four", "one", "three", "two"]。
    val numbersSortedSet = sortedSetOf("one", "two", "a","three", "four")
    println(numbersSortedSet.elementAt(0)) // 输出 a
    }

    还有一些有用的别名用于检索集合的第一个和最后一个元素:first()和last()。

    fun main() {
    val numbers = listOf("one", "two", "three", "four", "five")
    println(numbers.first()) // 输出为 one
    println(numbers.last()) // 输出为 five
    }

控制流程

与其他编程语言一样,Kotlin能够根据一段代码是否被评估为真来做出决定。这样的代码段称为条件表达式。Kotlin还能够创建循环并迭代循环。

条件表达式

Kotlin提供if和when来检查条件表达式。

  • if

    要使用if,请在括号()内添加条件表达式,并在花括号{}内添加结果为真时要采取的操作:

    fun main() {
    val d: Int
    val check = true if (check) { // 根据 check 来判断条件
    d = 1 // check 为 true 时,d赋值为 1
    } else {
    d = 2 // check 为 false 时,d赋值为 2
    } println(d) // 输出为 1
    }

    没有三元运算符条件?然后:在Kotlin中。相反,if可以用作表达式。如果每个操作只有一行代码,那么花括号{}是可选的:

    fun main() {
    val a = 1
    val b = 2
    // 如果 a 大于 b 输出为 a 否则为 b
    println(if (a > b) a else b) // 输出为 2
    }
  • when

    当您有一个具有多个分支的条件表达式时使用。

    在以下情况下使用:

    • 将要计算的值放在括号()内。

    • 将分支放在花括号{}内。

    • 在每个分支中使用->将每个检查与检查成功时要采取的操作分开。

    when既可以用作语句,也可以用作表达式。语句不返回任何内容,而是执行操作。

    以下是一个使用when作为语句的示例:

    fun main() {
    val obj = "Hello" when (obj) {
    // 当 obj 值为 "1" 时,输出 "One"
    "1" -> println("One")
    // 当 obj 值为 "Hello" 时,输出 "Greeting"
    "Hello" -> println("Greeting")
    // 当 obj 值都不满足的时候,输出 "Unknown"
    else -> println("Unknown")
    } }

循环

编程中最常见的两种循环结构是for和while。用于迭代一系列值并执行操作。使用while继续操作,直到满足特定条件。

  • for

    将迭代器和范围放在带关键字in的括号()内。在花括号{}内添加要完成的操作:

    fun main() {
    for (number in 1..5) {
    // number是迭代器,1..5是范围
    print(number)
    } // 输出 12345
    }

    集合也可以通过循环迭代:

    fun main() {
    val cakes = listOf("carrot", "cheese", "chocolate") for (cake in cakes) {
    println("Yummy, it's a $cake cake!")
    }
    /**
    * 输出
    * Yummy, it's a carrot cake!
    * Yummy, it's a cheese cake!
    * Yummy, it's a chocolate cake!
    * */ }
  • while

    while 以两种方式使用:

    • 在条件表达式为真时执行代码块。(while)

    • 先执行代码块,然后检查条件表达式。(do-while)

    在第一个用例中(while):

    • 声明while循环的条件表达式,以便在括号()内继续。

    • 在花括号{}内添加要完成的操作。

    fun main() {
    var cakesEaten = 0
    while (cakesEaten < 3) {
    println("Eat a cake")
    cakesEaten++
    }
    // 输出
    // Eat a cake
    // Eat a cake
    // Eat a cake
    }

    在第二个用例中(do-while):

    • 声明while循环的条件表达式,以便在括号()内继续。

    • 使用关键字do在花括号{}中定义要完成的操作。

      fun main() {
      var cakesEaten = 0
      var cakesBaked = 0
      while (cakesEaten < 3) {
      println("Eat a cake")
      cakesEaten++
      }
      do {
      println("Bake a cake")
      cakesBaked++
      } while (cakesBaked < cakesEaten)
      // 输出
      // Eat a cake
      // Eat a cake
      // Eat a cake
      // Bake a cake
      // Bake a cake
      // Bake a cake
      }

函数

我们可以使用fun关键字在Kotlin中声明自己的函数。

在Kotlin:

  • 函数参数写在括号()内。

  • 每个参数必须有一个类型,多个参数必须用逗号、分隔,。

  • 返回类型写在函数的括号()之后,用冒号分隔:。

  • 函数体是用花括号{}编写的。

  • return关键字用于从函数中退出或返回某些内容。

在以下示例中:

  • x和y是函数参数。

  • x和y的类型为Int。

  • 函数的返回类型为Int。

  • 该函数在调用时返回x和y的和。

fun sum(x: Int, y: Int): Int {
return x + y
} fun main() {
println(sum(1, 2)) // 输出 3
}

对于简洁的代码,在调用函数时,不必包含参数名。但是,包含参数名称确实会使我们的代码更容易阅读。这是使用命名参数调用的。如果确实包含参数名称,则可以按任何顺序写入参数。

fun printMessageWithPrefix(message: String, prefix: String) {
println("[$prefix] $message")
} fun main() {
// 使用具有交换参数顺序的命名参数
printMessageWithPrefix(prefix = "Log", message = "Hello") // 输出 [Log] Hello
}

我们可以为函数参数定义默认值。调用函数时,可以省略任何具有默认值的参数。要声明默认值,请在类型后使用赋值运算符=:

fun printMessageWithPrefix(message: String, prefix: String = "Info") {
println("[$prefix] $message")
} fun main() {
// 使用两个参数调用函数
printMessageWithPrefix("Hello", "Log") // 输出 [Log] Hello // 仅使用消息参数调用函数
printMessageWithPrefix("Hello") // 输出 [Info] Hello printMessageWithPrefix(prefix = "Log", message = "Hello") // 输出 [Log] Hello
}

如果我们的函数没有返回有用的值,那么它的返回类型是Unit。Unit是一种只有一个值的类型——Unit。您不必在函数体中明确声明Unit返回。这意味着您不必使用return关键字或声明返回类型:

fun printMessage(message: String) {
println(message) // `return Unit` or `return` is optional
} fun main() {
printMessage("Hello") // 输出 Hello
}

要阻止函数中的代码被进一步处理超过某个点,请使用return关键字。这个例子使用if,如果发现条件表达式为真,则提前从函数返回:

// 已注册用户名列表
val registeredUsernames = mutableListOf("john_doe", "jane_smith") // 已注册电子邮件列表
val registeredEmails = mutableListOf("john@example.com", "jane@example.com") fun registerUser(username: String, email: String): String {
// 如果用户名已被占用,则提前返回
if (username in registeredUsernames) {
return "用户名已被占用,请选择其他用户名。"
} // 如果电子邮件已注册,则提前返回
if (email in registeredEmails) {
return "电子邮件已注册,请使用其他电子邮件。"
} // 如果用户名和电子邮件未被使用,请继续注册
registeredUsernames.add(username)
registeredEmails.add(email) return "用户注册成功: $username"
} fun main() {
println(registerUser("john_doe", "newjohn@example.com")) // 输出 用户名已被占用,请选择其他用户名。
println(registerUser("new_user", "newuser@example.com")) // 输出 用户注册成功: new_user
}

Kotlin允许我们使用lambda表达式为函数编写更简洁的代码。

例如,以下uppercaseString()函数:

//fun uppercaseString(text: String): String {
// return text.uppercase()
//}
fun main() {
// 正常使用
// println(uppercaseString("hello")) // 输出 HELLO
// Lambada表达式
val uppercaseString= { text: String -> text.uppercase()}
println(uppercaseString("hello")) // 输出 HELLO
}

Lambda表达式乍一看可能很难理解,所以让我们把它分解一下。Lambda表达式用花括号 {} 编写。

在Lambda表达式中,你可以写:

参数后面跟着 ->。

-> 后的函数体。

在上面的示例中:

text是一个函数参数。

文本的类型为String。

该函数返回对文本调用的 .uppercase()函数的结果。

使用赋值运算符=将整个lambda表达式赋值给upperCaseString变量。

lambda表达式是通过将变量upperCaseString用作函数并将字符串“hello”用作参数来调用的。

println()函数打印结果。

Lambda表达式可以以多种方式使用。我们可以:

  • 将lambda表达式作为参数传递给另一个函数

  • 从函数返回lambda表达式

  • 自行调用lambda表达式

转到另一个函数

当将lambda表达式传递给函数时,一个很好的例子是在集合上使用.filter()函数:

fun main() {
val numbers = listOf(1, -2, 3, -4, 5, -6) val isPositives = { x: Int -> x > 0}
val positives = numbers.filter(isPositives) // val positives = numbers.filter { x -> x > 0 } // 筛选出列表中大于0的元素 // val isNegative = { x: Int -> x < 0 }
// val negatives = numbers.filter(isNegative) val negatives = numbers.filter { x -> x < 0 } println(positives) // 输出 [1, 3, 5]
println(negatives) // 输出 [-2, -4, -6] /**
* .filter()函数接受lambda表达式作为谓词:
*
* {x->x>0}接受列表中的每个元素,只返回正的元素。
*
* {x->x<0}接受列表中的每个元素,只返回负数。
* */
}

此示例演示了将lambda表达式传递给函数的两种方法:

对于正数,该示例直接在.filter()函数中添加lambda表达式。

对于负数,该示例将lambda表达式赋给isNegative变量。然后将isNegative变量用作.filter()函数中的函数参数。在这种情况下,我们必须在lambda表达式中指定函数参数(x)的类型。

另一个很好的例子是使用.map()函数来转换集合中的项:

fun main() {
val numbers = listOf(1, -2, 3, -4, 5, -6)
val doubled = numbers.map { x -> x * 2 } val isTripled = { x: Int -> x * 3 }
val tripled = numbers.map(isTripled) println(doubled) // 输出为 [2, -4, 6, -8, 10, -12]
println(tripled) // 输出为 [3, -6, 9, -12, 15, -18] /**
* .map()函数接受lambda表达式作为转换函数:
*
* {x->x*2}获取列表中的每个元素,并返回该元素乘以2。
*
* {x->x*3}获取列表中的每个元素,并返回该元素乘以3。
* */
}

函数返回

Lambda表达式可以从函数返回。为了让编译器理解返回的lambda表达式是什么类型,我们必须声明一个函数类型。

在下面的示例中,toSeconds()函数的函数类型为(Int)->Int,因为它总是返回一个lambda表达式,该表达式接受Int类型的参数并返回Int值。

此示例使用when表达式来确定调用toSeconds()时返回哪个lambda表达式:

fun toSeconds(time: String): (Int) -> Int = when (time) {
"hour" -> { value -> value * 60 * 60 }
"minute" -> { value -> value * 60 }
"second" -> { value -> value }
else -> { value -> value }
} fun main() {
val timesInMinutes = listOf(2, 10, 15, 1)
val min2sec = toSeconds("minute")
val totalTimeInSeconds = timesInMinutes.map(min2sec).sum()
println("Total time is $totalTimeInSeconds secs") // 输出 Total time is 1680 secs }

Kotlin支持使用类和对象的面向对象编程。对象对于在程序中存储数据很有用。类允许我们为对象声明一组特征。

要声明一个类,请使用class关键字:

class Customer

类对象的特性可以在属性中声明。我们可以声明类的属性:

class Contact(val id: Int, var email: String)

在由花括号{}定义的类体内。

class Contact(val id: Int, var email: String) {
val category: String = ""
}

我们建议将属性声明为只读(val),除非在创建类的实例后需要更改它们。

我们可以在括号内声明没有val或var的属性,但在创建实例后无法访问这些属性。

括号()中包含的内容称为类头。

声明类属性时可以使用尾随逗号。

就像函数参数一样,类属性也可以有默认值:

class Contact(val id: Int, var email: String = "example@gmail.com") {
val category: String = "work"
}

要从类创建对象,您需要使用构造函数声明类实例。

默认情况下,Kotlin会自动创建一个构造函数,其中包含类头中声明的参数。

例如:

class Contact(val id: Int, var email: String)

fun main() {
val contact = Contact(1, "mary@gmail.com")
}

在示例中:

  • Contact是一个类

  • contact是Contact类的一个实例。

  • id和email是属性。

  • id和email与默认构造函数一起用于创建contact。

要访问实例的属性,请在实例名称后添加句点,然后写入属性名称

class Contact(val id: Int, var email: String)

fun main() {
val contact = Contact(1, "mary@gmail.com") // 打印输出 contact 对象的 email 属性
println(contact.email) // 输出为 mary@gmail.com // 更新 contact 对象的 email 属性
contact.email = "jane@gmail.com" // 打印输出 contact 对象的 email 属性
println(contact.email) // 输出为 jane@gmail.com
}

除了将属性声明为对象特性的一部分外,我们还可以使用成员函数定义对象的行为。

在Kotlin中,成员函数必须在类体内声明。要在实例上调用成员函数,请在实例名称后加上句点 . 例如:

class Contact(val id: Int, var email: String) {
fun printId() {
println(id)
}
} fun main() {
val contact = Contact(1, "mary@gmail.com")
// 调用 Contact 类中的方法
contact.printId() // 输出 1
}

Kotlin的数据类对于存储数据特别有用。数据类与类具有相同的功能,但它们会自动附带其他成员函数。这些成员函数允许您轻松地将实例打印为可读输出,比较类的实例,复制实例等。由于这些函数是自动可用的,我们不必花时间为每个类编写相同的样板代码。

要声明数据类,请使用关键字data:

data class User(val name: String, val id: Int)

数据类最有用的预定义成员函数是:

功能 描述
toString() 打印类实例以及其属性的可读字符串
equals() 或者 == 比较某个类的实例
copy() 通过复制另一个类实例来创建一个类实例,可能具有一些不同的属性

打印为字符串

要打印类实例的可读字符串,您可以显式调用toString()函数,或使用打印函数(println()和print()),这些函数会自动为您调用toStrings():

data class User(val name: String, val id: Int)

fun main() {
val user = User("Alex", 1) // 自动使用toString()函数,使输出易于阅读
println(user) // 输出为 User(name=Alex, id=1)
}

这在调试或创建日志时特别有用。

比较实例

要比较数据类实例,请使用等式运算符==:

data class User(val name: String, val id: Int)

fun main() {
val user = User("Alex", 1)
val secondUser = User("Alex", 1)
val thirdUser = User("Max", 2) // 比较 user 和 secondUser 两个实例是否相同
println("user == secondUser: ${user == secondUser}") // 输出 user == secondUser: true // 比较 user 和 thirdUser 两个实例是否相同
println("user == thirdUser: ${user == thirdUser}") // 输出 user == thirdUser: false }

复制实例

要创建数据类实例的精确副本,请在实例上调用copy()函数。

要创建数据类实例的副本并更改某些属性,请在实例上调用copy()函数,并为属性添加替换值作为函数参数。

例如:

data class User(val name: String, val id: Int)

fun main() {
val user = User("Alex", 1) // 创建 user 的精确副本
println(user.copy()) // 输出 User(name=Alex, id=1) // 创建名为“Max”的 user 副本
println(user.copy("Max")) // 输出 User(name=Max, id=1) // 创建 id 为 3 的 user 的副本
println(user.copy(id = 3)) // 输出 User(name=Alex, id=3) }

空安全

在Kotlin中,可以有一个空值。Kotlin在缺少或尚未设置某些内容时使用空值。我们在之前看到过Kotlin返回null值的示例,当我们试图使用映射中不存在的键访问键值对时。虽然以这种方式使用null值很有用,但如果您的代码没有准备好处理它们,可能会遇到问题。

为了帮助防止程序中出现空值问题,Kotlin提供了空安全机制。空安全在编译时而不是运行时检测空值的潜在问题。

零安全是一系列功能的组合,允许我们:

  • 明确声明程序中何时允许空值。

  • 检查是否为空值。

  • 使用对可能包含空值的属性或函数的安全调用。

  • 声明检测到空值时要采取的操作。

Kotlin支持可以为null的类型,这允许声明的类型具有null值。默认情况下,不允许类型接受null值。通过显式添加来声明可为null的类型 ’?‘ 在类型声明之后。

例如:

fun main() {
// neverNull 具有String类型
var neverNull: String = "This can't be null" // 抛出编译器错误
// neverNull = null // nullable 具有可为null的String类型
var nullable: String? = "You can keep a null here" // 没有问题
nullable = null // 默认情况下,不接受空值
var inferredNonNull = "The compiler assumes non-nullable" // 抛出编译器错误
// inferredNonNull = null // notNull不接受空值
fun strLength(notNull: String): Int {
return notNull.length
} println(strLength(neverNull)) // 18
// 抛出编译器错误
// println(strLength(nullable))
}

我们可以检查条件表达式中是否存在空值。在下面的示例中,describeString()函数有一个if语句,用于检查maybeString是否不为null,以及其长度是否大于零:

fun describeString(maybeString: String?): String {
if (maybeString != null && maybeString.length > 0) {
return "字符串长短 ${maybeString.length}"
} else {
return "空字符串"
}
} fun main() {
val nullString: String? = null
println(describeString(nullString)) // 输出为 空字符串
}

要安全地访问可能包含空值的对象的属性,请使用safe call运算符 '?.' 。如果对象或其访问的属性之一为null,则安全调用运算符返回null。如果想避免空值在代码中引发错误,这很有用。

在以下示例中,length string()函数使用安全调用返回字符串的长度或null:

fun lengthString(maybeString: String?): Int? = maybeString?.length

fun main() {
val nullString: String? = null
println(lengthString(nullString)) // 输出 null
}

安全调用运算符也可用于安全调用分机或成员函数。在这种情况下,在调用函数之前会执行null检查。如果检查检测到null值,则跳过调用并返回null。

在以下示例中,nullString为null,因此跳过对.uppercase()的调用并返回null:

fun main() {
val nullString: String? = null
println(nullString?.uppercase()) // 输出为 null
}

如果使用Elvis运算符?:检测到空值,我们可以提供一个默认值来返回。

在Elvis运算符的左侧写下应检查空值的内容。在Elvis运算符的右侧写下如果检测到空值应该返回什么。

在以下示例中,nullString为null,因此访问length属性的安全调用返回null值。因此,Elvis运算符返回0:

fun main() {
val nullString: String? = null
println(nullString?.length ?: 0) // 输出 0
}

学习Kotlin语法(一)的更多相关文章

  1. 推荐两份学习 Kotlin 和机器学习的资料

    最近 Kotlin 和人工智能比较火,有不少同学留言问我怎么学习 Kotlin,怎么学习机器学习,今天就给大家推荐两份不错的学习资料. 1. Kotlin 学习资料其实,在我看来最好的学习资料就是 K ...

  2. 五分钟学会 Kotlin 语法

    为什么使用Kotlin 项目一期在收尾了终于有时间折腾了,一个多月以来Kotlin从入门到现在,坚持用来开发的切身感受.因为语法与Java的区别挺大的一开始很想放弃,如果不是因为项目在使用,想必很少人 ...

  3. ios -- 教你如何轻松学习Swift语法(一)

    目前随着公司开发模式的变更,swift也显得越发重要,相对来说,swift语言更加简洁,严谨.但对于我来说,感觉swift细节的处理很繁琐,可能是还没适应的缘故吧.基本每写一句代码,都要对变量的数据类 ...

  4. 程序员带你学习安卓开发,十天快速入-对比C#学习java语法

    关注今日头条-做全栈攻城狮,学代码也要读书,爱全栈,更爱生活.提供程序员技术及生活指导干货. 如果你真想学习,请评论学过的每篇文章,记录学习的痕迹. 请把所有教程文章中所提及的代码,最少敲写三遍,达到 ...

  5. 工作效率-十五分钟让你快速学习Markdown语法到精通排版实践备忘

    关注「WeiyiGeek」公众号 设为「特别关注」每天带你玩转网络安全运维.应用开发.物联网IOT学习! 希望各位看友[关注.点赞.评论.收藏.投币],助力每一个梦想. 文章目录: 0x00 前言简述 ...

  6. kotlin 语法跟 java 的不同

    本文是本人的作品,转载请表明出处 1.extends  用 (冐号):代替.MainActivity extends Activity, 现在是  MaiActivity :Activity() 2. ...

  7. Groovy学习--基本语法了解

    x项目用到gradle,学习gradle之前准备先过一遍Groovy的语法.这里参考:Groovy入门. 该博客没有系统的讲解Groovy的语法和原理,仅仅只是罗列了使用Groovy的常规方法.我照着 ...

  8. Kotlin语法(函数和lambda表达式)

    三.函数和lambda表达式 1. 函数声明 fun double(x: Int): Int { } 函数参数是用 Pascal 符号定义的 name:type.参数之间用逗号隔开,每个参数必须指明类 ...

  9. ios -- 教你如何轻松学习Swift语法(三) 完结篇

    前言:swift语法基础篇(二)来了,想学习swift的朋友可以拿去参考哦,有兴趣可以相互探讨,共同学习哦.      一.自动引用计数   1.自动引用计数工作机制      1.1 swift和o ...

  10. ios -- 教你如何轻松学习Swift语法(二)

    前言:swift语法基础篇(二)来了,想学习swift的朋友可以拿去参考哦,有兴趣可以相互探讨,共同学习哦.      一.可选类型(重点内容)   1.什么是可选类型?        1.1在OC开 ...

随机推荐

  1. 时间序列数据库TSDB InfluxDB介绍

    背景 这两年互联网行业掀着一股新风,总是听着各种高大上的新名词.大数据.人工智能.物联网.机器学习.商业智能.智能预警啊等等. 以前的系统,做数据可视化,信息管理,流程控制.现在业务已经不仅仅满足于这 ...

  2. 如何快速的开发一个完整的iOS直播app(采集篇)

    作者:袁峥链接:https://www.jianshu.com/p/c71bfda055fa来源:简书著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. 开发一款直播app,首先需要 ...

  3. ctfshow--web4 include日志注入

    这题和第三题有点不一样,这题的把php 和 data 都过滤掉了 一旦我们输入这个关键字就页面就会报error 一开始是没啥头绪的,后面上网查了一下,可以通过日志记录来注入代码 对于Apache,日志 ...

  4. dart箭头函数和自执行函数的详解

    01==>箭头函数 // List list = ['苹果', '香蕉', '栗子']; // list.forEach((element) { // print(element); // }) ...

  5. Ubuntu Linux部署DeepSeek

    技术背景 DeepSeek是这段时间最热门的话题之一,其蒸馏模型可以实现低成本而高质量的推理,使得我们现在可以在本地小型化的硬件上也用上大模型这一AI利器.本文主要介绍通过Ollama来部署DeepS ...

  6. 天翼云VPC支持专线健康检查介绍

    本文分享自天翼云开发者社区<天翼云VPC支持专线健康检查介绍>,作者:汪****波 天翼云支持本地数据中心IDC(Internet Data Center)通过冗余专线连接到天翼云云上专有 ...

  7. pg数据库性能优化(转)

    参数修改的方式 1.修改配置文件 在配置文件data目录下postgresql.conf 中直接修改,修改前记得备份一下原文件.修改完成之后,记得重启数据库哦. 2.命令行的修改方式 ALTER SY ...

  8. 干货:DeepSeek+SpringAI实现流式对话!

    前面一篇文章我们实现了<炸裂:SpringAI内置DeepSeek啦!>,但是大模型的响应速度通常是很慢的,为了避免用户用户能够耐心等待输出的结果,我们通常会使用流式输出一点点将结果输出给 ...

  9. 大模型工具KTransformer的安装

    技术背景 前面写过几篇关于DeepSeek的文章,里面包含了通过Ollama来加载模型,以及通过llama.cpp来量化模型(实际上Llama.cpp也可以用来加载模型,功能类似于Ollama).这里 ...

  10. [TJOI2015] 弦论 题解

    所有子串,一眼 \(\text{SAM}\). 从根开始一直往下走,走到任何一个点都代表一个子串.维护 \(sm\) 表示每个子串有几个(\(t=0\) 就当一个),可以用树形 \(dp\) 跳后缀链 ...