学习Kotlin语法(一)
简介Kotlin是一种当代、简洁且功能强大的编程语言,特别得当Android开发。本文将从基础语法开始,渐渐把握Kotlin的核心特性。
目录
[*]变量
[*]根本类型
[*]数字
[*]无符号对应项
[*]布尔值
[*]字符和字符串
[*]数组
[*]集合
[*]集合类型
[*]List
[*]Set
[*]Map
[*]ArrayDeque
[*]构建集合
[*]从元素构建
[*]利用集合构建函数创建
[*]空集合
[*]列表的初始化函数
[*]详细类型构造函数
[*]集合的复制
[*]调用其他集合上的函数
[*]迭代器
[*]列表迭代器
[*]可变迭代器
[*]筛选
[*]按谓词筛选
[*]分割
[*]分组
[*]检索
[*]控制流程
[*]条件表达式
[*]循环
[*]函数
[*]类
[*]空安全
学习语法都将从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) // 输出为
list.add(4)
println(list) // 输出为
list = mutableListOf(5, 6, 7) // 编译器报错,因为是只读变量,不能被重新赋值
var list2 = mutableListOf(1, 2, 3)
println(list2) // 输出为
list2.add(4)
println(list2) // 输出为
list2 = mutableListOf(5, 6, 7) // 因为是可变变量, 所以可以重新赋值
println(list2) // 输出为
}
在编写代码中,优先利用 val ,除非确实须要改变变量的值,并且利用 val 可以提高代码的可读性和安全性,避免意外的变量修改。通过公道的利用 val 和 var,可以更好的管理变量的可变性,提升代码的结实性。
根本类型
在Kotlin中的每个变量和数据结构都有类型。类型很重要,由于它们告诉编译器可以对该变量大概数据结构实行什么操纵。换句话说,它具有哪些函数和属性。
在Kotlin中,一切皆为对象,由于我们可以调用任何变量的成员函数和属性。固然某些类型在运行时具有优化的内部表示情势(如数字、字符、布尔值等),但它们对开发者来说看起来和表现起来都香通例类。
接下来先容一下Kotlin中利用的根本类型:
数字(Numbers)
[*] 整数类型
Kotlin提供了一组表示数字的内置类型,对于整数,有四种类型,它们的大小和值范围差别:
类型位数最小值最大值Byte8-128127Short16-3276832767Int32-2,147,483,648 (-2^31)2,147,483,647 (2^31 - 1)Long64-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反映双精度。
这些类型的大小差别,为差别精度的浮点数提供存储空间:
类型位数有效位指数位小数位Flot322386-7Double64531115-16fun 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还为无符号整数提供了以下类型:
类型位数最小值最大值UByte80255UShort16065,535UInt3204,294,967,295 (2^32 - 1)ULong64018,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。我们可以使用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:
/**
* 或花括号中的表达式:
* */
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() {
// 创建一个数组
val sampleArray = arrayOf(1, 2, 3)
println(sampleArray.joinToString()) // 输出为 1, 2, 3
// 创建一个数组
val nullArray: Array<Int?> = arrayOfNulls(3)
println(nullArray.joinToString()) // 输出为 null, null ,null
// 创建一个用零初始化的 Array<Int>
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()) // 输出为 [, ]
// 创建三维数组
val threeDArray = Array(3) { Array(3) { Array<Int>(3) { 0 } } }
println(threeDArray.contentDeepToString()) // 输出为 [[, , ], [, , ], [, , ]]
}
嵌套数组不必是相同的类型或大小。
[*] 访问和修改元素
数组始终是可变的。要访问和修改数组中的元素,可以利用索引访问运算符[]:
fun main() {
val simpleArray = arrayOf(1, 2, 3)
val twoDArray = Array(2) { Array<Int>(2) { 0 } }
println(simpleArray.toString()) // 输出为 11
println(twoDArray.toString()) // 输出为 0
// 访问元素并修改它
simpleArray = 10
twoDArray = 2
println(simpleArray.toString()) // 输出为 10
println(twoDArray.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 = 10
println(simpleArray contentEquals anotherArray) // 结果为 false
}
不要利用等式(==)和不等式(!=)运算符来比力数组的内容。这些运算符查抄分配的变量是否指向同一对象。
[*] 原始类型数组
如果将Array类与基元值一起利用,这些值将被装箱到对象中。作为替代方案,我们可以利用基元类型数组,这答应我们在数组中存储基元,而不会产生装箱开销的副作用:
原始类型数组Java中对应代码BooleanArrayboolean[]ByteArraybyte[]CharArraychar[]DoubleArraydouble[]FloatArrayfloat[]IntArrayint[]LongArraylong[]ShortArrayshort[]
集合
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) // 输出
numbers.add("Five")
println(numbers) // 输出
numbers = mutableListOf("Six", "Seven") // 编译报错 Val cannot be reassigned
}
只读集合类型是协变的。这意味着,如果Rectangle类继承自Shape,则可以在须要List的任何地方利用List。换句话说,集合类型与元素类型具有相同的子类型关系。映射在值类型上是协变的,但在键类型上不是协变的。
反过来,可变集合不是协变的;否则,这将导致运行时失败。如果MutableList是MutableList的子类型,则可以将其他Shape继承器(例如Circle)插入此中,从而违反其Rectangle类型参数。
下面是Kotlin集合接口的表示图:
https://i-blog.csdnimg.cn/img_convert/4f3f506b2a6f463c46a54528dd55e950.png
List
**List**按指定顺序存储元素,并提供对它们的索引访问。索引从零开始,即第一个元素的索引,然后转到lastIndex,即(list.size-1)。
fun main() {
val numbers = mutableListOf("One", "Two", "Three", "Four")
println("数组大小: ${numbers.size}") // 输出 数组大小: 4
println("第三个元素: ${numbers}") // 输出 第三个元素: Three
println("第四个元素: ${numbers}") // 输出 第四个元素: 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) // 输出为
numbers.add(5)
println(numbers) // 输出为
numbers.removeAt(1)
println(numbers) // 输出为
numbers = 0
println(numbers) // 输出为
numbers.shuffle()
println(numbers) // 输出为
}
如上述所见,在某些方面,列表与数组非常相似。然而,有一个重要的区别:数组的大小是在初始化时定义的,永远不会改变;反过来,列表没有预定义的大小;列表的大小可以因写入操纵而改变:添加、更新或删除元素。
在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:
println("所有values: ${numbersMap.values}") // 输出为 所有values:
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) // 输出为
deque.addFirst(0)
println(deque) // 输出为
deque.addLast(4)
println(deque) // 输出为
println(deque.first()) // 输出为 0
println(deque.last()) // 输出为 4
deque.removeFirst()
println(deque) // 输出为
deque.removeLast()
println(deque) // 输出为
}
构建集合
从元素构建
创建集合的最常见方法是利用标准库函数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) // 输出为
}
详细类型构造函数
要创建详细的类型集合,如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.name}, 复制集合中第一个Item的值为: ${copyList.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) // 输出为
}
大概,可以创建对同一集合实例的新引用。当您利用现有集合初始化集合变量时,会创建新的引用。因此,当通过引用更改集合实例时,这些更改会反映在其全部引用中。
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) // 输出为
}
调用其他集合上的函数
可以通过对其他集合进行各种操纵来创建集合。例如,过滤列表会创建一个与过滤器匹配的新元素列表:
fun main() {
val numbers = listOf("one", "two", "three", "four")
val longerThan3 = numbers.filter { it.length > 3 }
println(longerThan3) // 输出为
}
映射根据转换的效果生成一个列表:
fun main() {
val numbers = setOf(1, 2, 3)
println(numbers.map { it * 3 }) // 输出为
println(numbers.mapIndexed {idx, value -> value * idx}) // 输出为
}
迭代器
对于遍历集合元素,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") // 输出为 删除之后:
}
除了删除元素外,MutableListIterator还可以在利用add()和set()函数迭代列表时插入和替换元素。
fun main() {
val numbers = mutableListOf("one", "four", "four")
val mutableListIterator = numbers.listIterator()
mutableListIterator.next()
mutableListIterator.add("two")
println(numbers) // 输出为
mutableListIterator.next()
mutableListIterator.set("three")
println(numbers) // 输出为
}
筛选
筛选是收集集合中最受接待的使命之一。在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的有:
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的有:
val filteredNot = numbers.filterNot { it.length <= 3 }
println("字符长度大于3的有: $filteredNot") // 输出为 字符长度大于3的有:
}
还有一些函数通过过滤给定类型的元素来缩小元素类型:
[*] 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) // 输出为
println(rest) // 输出为
}
分组
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=, T=, F=}
// 遍历每个组并打印密钥及其相关值
for ((key, value) in groupedByFirstLetter) {
println("Key: $key, Values: $value")
}
// 输出为
// Key: O, Values:
// Key: T, Values:
// Key: F, Values:
// 按字符串的第一个字母对其进行分组,并将值转换为大写
val groupedAndTransformed = numbers.groupBy(keySelector = { it.first() }, valueTransform = { it.uppercase() })
println(groupedAndTransformed) // 输出为 {o=, t=, f=}
}
如果想对元素进行分组,然后一次对全部组应用一个操纵,请利用函数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的元素 输出为
println(numbers.slice(0..4 step 2)) // 切出位于从0开始,间隔2的元素 输出为
println(numbers.slice(setOf(3, 5, 0))) // 切出位于3、5、0的元素 输出为
}
[*] Take and drop
要从第一个开始获取指定命量的元素,请利用take()函数。要获取最后一个元素,请利用takeLast()。当利用大于集合大小的数字调用时,这两个函数都会返回整个集合。
要获取除给定命量的第一个或最后一个元素之外的全部元素,请分别调用drop()和dropLast()函数。
fun main() {
val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.take(3)) // 从头拿取3个元素 输出为
println(numbers.takeLast(3)) // 从后拿取3个元素 输出为
println(numbers.drop(1)) // 丢弃去第一个元素 输出为
println(numbers.dropLast(4)) // 从后丢弃4个元素 输出为
}
我们还可以利用谓词来定义取或放的元素数量。有四个功能与上述功能相似:
[*] 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’ 输出为
println(numbers.takeLastWhile { it != "three" }) // 从后拿取元素,直到元素为‘three’ 输出为
println(numbers.dropWhile { it.length == 3 }) // 从头丢弃元素,直到元素长度大于3 输出为
println(numbers.dropLastWhile { it.contains('i') }) // 从后丢弃元素,直到元素不含有i 输出为
}
[*] chunked
要将集合分解为给定大小的部分,请利用chunked()函数。chunked()接受一个参数——块的大小——并返回给定大小的列表列表。第一个块从第一个元素开始,包含大小元素,第二个块包含下一个大小元素,以此类推。最后一个块的大小可能较小。
fun main() {
val numbers = (0..13).toList()
println(numbers.chunked(3)) // 分解为三个元素为一组的列表,在返回一个包含子列表的列表 输出为 [, , , , ]
}
我们还可以立即对返回的块应用转换。为此,在调用chunked()时,将转换作为lambda函数提供。lambda参数是集合的一块。当利用转换调用chunked()时,chunks是短生命列表,应该在该lambda中利用。
fun main() {
val numbers = (0..13).toList()
println(numbers.chunked(3)) // 输出为 [, , , , ]
println(numbers.chunked(3) { it.sum() }) // 将分割的每组数据累加后,在返回成一个新的列表 输出为
}
[*] windowed
我们可以检索给定大小的集合元素的全部可能范围。获取它们的函数称为windowed():它返回一个元素范围列表,如果你通过给定大小的滑动窗口查看集合,你会看到这些元素范围。与chunked()差别,windowed()返回从每个集合元素开始的元素范围(窗口)。全部窗口都作为单个List的元素返回。
fun main() {
val numbers = listOf("one", "two", "three", "four", "five")
println(numbers.windowed(2)) // 输出为 [, , , ]
println(numbers.windowed(3)) // 输出为 [, , ]
println(numbers.windowed(4)) // 输出为 [, ]
}
windowed()通过可选参数提供了更大的灵活性:
该步骤定义了两个相邻窗口的第一元素之间的间隔。默认环境下,该值为1,因此效果包含从全部元素开始的窗口。如果将步长增加到2,则只会收到从奇数元素开始的窗口:第一、第三等。
partialWindows包括从集合末尾的元素开始的较小大小的窗口。例如,如果您请求三个元素的窗口,则无法为最后两个元素构建它们。在这种环境下启用partialWindows包括另外两个大小为2和1的列表。
最后,可以立即对返回的范围应用转换。为此,在调用windowed()时,将转换作为lambda函数提供。|
fun main() {
val numbers = (1..10).toList()
println(numbers.windowed(3, step = 2, partialWindows = true)) // 输出为 [, , , , ]
println(numbers.windowed(3) { it.sum() }) // 输出为
}
要构建两个元素窗口,有一个单独的函数zipWithNext()。它创建接收器集合的相邻元素对。请注意,zipWithNext()不会将集合分成对;它为除最后一个元素之外的每个元素创建一个Pair,因此它在上的效果是[,,],而不是[,]。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}) // 每个子集合中元素进行比较 输出为
}
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") // 输出 Hello
}
我们可以为函数参数定义默认值。调用函数时,可以省略任何具有默认值的参数。要声明默认值,请在类型后利用赋值运算符=:
fun printMessageWithPrefix(message: String, prefix: String = "Info") {
println("[$prefix] $message")
}
fun main() {
// 使用两个参数调用函数
printMessageWithPrefix("Hello", "Log") // 输出 Hello
// 仅使用消息参数调用函数
printMessageWithPrefix("Hello") // 输出 Hello
printMessageWithPrefix(prefix = "Log", message = "Hello") // 输出 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) // 输出
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) // 输出为
println(tripled) // 输出为
/**
* .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支持可以为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
}
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]