一、常量与变量
1.1 定义变量和常量
Kotlin 中定义变量和常量有两种关键字:val 和 var。
val:不可变引用(相当于 Java 中的final变量)。var:可变引用。
val constantValue = 42 // 常量
var variableValue = 42 // 变量
variableValue = 43 // 变量可以重新赋值
1.2 类型推断
Kotlin 有强大的类型推断功能,在定义变量和常量时可以省略类型声明,编译器会自动推断类型:
val constantValue = 42 // 编译器推断类型为 Int
var variableValue = "Hello" // 编译器推断类型为 String
如果需要显式声明类型,也可以这样写
val constantValue: Int = 42
var variableValue: String = "Hello"
1.3 延迟初始化
对于 var 变量,如果在定义时无法立即赋值,可以使用 lateinit 关键字延迟初始化。lateinit 只能用于可变的非空类型的属性,并且不能用于原始类型(如 Int、Float)。
lateinit var name: String
fun initializeName() {
name = "Kotlin"
}
fun printName() {
if (this::name.isInitialized) {
println(name)
} else {
println("Name is not initialized")
}
}
对于不可变类型(val),可以使用 lazy 委托属性进行延迟初始化,lazy 初始化的代码块只会在第一次访问该属性时执行:
val name: String by lazy {
println("Computing the name...")
"Kotlin"
}
fun main() {
println(name) // 第一次访问,执行 lazy 代码块,输出 "Computing the name..." 和 "Kotlin"
println(name) // 后续访问,不再执行 lazy 代码块,只输出 "Kotlin"
}
1.4 不可变集合
Kotlin 提供了不可变集合(如 List、Set、Map),这些集合类型是使用 val 定义的,不能改变其引用,但集合本身可以是可变的:
val list = listOf(1, 2, 3) // 不可变列表
val mutableList = mutableListOf(1, 2, 3) // 可变列表
mutableList.add(4) // 可以修改可变列表的内容
1.5 编译时常量与运行时常量
-
编译时常量:使用
const val声明的常量,在编译期确定其值,必须是顶层属性或者object中的属性,且类型必须是基本类型或 String。 -
运行时常量:使用
val声明的常量,其值在运行时确定,可以是任意类型。
const val COMPILE_TIME_CONSTANT = 42 // 编译时常量
val runtimeConstant: Int
get() {
// 一些运行时计算
return (1..100).random()
}
二、函数
2.1 基本定义
Kotlin 中定义函数使用 fun 关键字,函数可以有返回类型,也可以没有返回类型。
fun sum(a: Int, b: Int): Int {
return a + b
}
//实际上,`printSum` 函数返回 `Unit`,但在函数声明中可以省略 `Unit` 返回类型
//Unit 它是一个对象
fun printSum(a: Int, b: Int) {
println("Sum of $a and $b is ${a + b}")
}
简写形式:
fun sum(a: Int, b: Int) = a + b
2.2 局部函数
Kotlin 允许在函数内部定义函数,这种函数称为局部函数,通常用于封装逻辑。
fun outerFunction(a: Int, b: Int) {
fun innerFunction(x: Int): Int {
return x * x
}
val result = innerFunction(a) + innerFunction(b)
println("Result: $result")
}
编译后的 Java 代码(示例) 编译后的 Java 代码将展示局部函数如何被转换为静态私有方法。
public final class ExampleKt {
public static final void outerFunction(int a, int b) {
int result = ExampleKt.innerFunction(a) + ExampleKt.innerFunction(b);
System.out.println("Result: " + result);
}
private static final int innerFunction(int x) {
return x * x;
}
public static void main(String[] args) {
outerFunction(2, 3); // 输出 "Result: 13"
}
}
2.3 扩展函数
扩展函数允许在不修改现有类的情况下向其添加新功能。
fun String.addExclamation() = this + "!" //扩展了String类
val message = "Hello".addExclamation()
println(message) // 输出 "Hello!"
2.4 内联函数
内联函数使用 inline 关键字,可以减少高阶函数带来的性能开销。
内联函数原理
当一个函数被标记为 inline 时,Kotlin 编译器会在编译时将该函数的代码替换到每个调用该函数的地方。这意味着函数调用的开销被消除了,减少了 Lambda 表达式和函数对象的创建。
inline fun inlineCalculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
* 2.4.1 使用内联函数的注意事项
-
代码膨胀: 内联函数会将函数体复制到每个调用点,如果函数体非常大或被频繁调用,可能会导致代码膨胀(code bloat)。这可能会增加最终生成的字节码大小。
-
禁止内联: 并非所有函数都适合内联。可以使用
noinline关键字来禁止某些 Lambda 表达式内联。假设我们有一个高阶函数,它接受两个 Lambda 表达式作为参数,并且我们希望其中一个 Lambda 表达式不被内联:
inline fun <T> execute(block: () -> T, noinline onComplete: (T) -> Unit): T {
val result = block()
onComplete(result)
return result
}
- 递归函数:递归函数不能标记为
inline,因为内联会导致无限递归的展开,从而导致编译失败。 - 非局部返回:在内联函数中,Lambda 表达式可以非局部返回(即从包含它的函数返回)。如果不希望允许非局部返回,可以使用
crossinline关键字。
inline fun inlineFunction(block: () -> Unit) {
println("Before block")
block()
println("After block")
}
fun main() {
inlineFunction {
println("Inside block")
return // 非局部返回,直接返回到 main 函数
}
println("This will not be printed")
}
在上面的例子中,return 语句直接从 main 函数返回,导致 println("This will not be printed") 永远不会执行。
现在,来看一下 crossinline 如何防止非局部返回:
inline fun inlineFunction(crossinline block: () -> Unit) {
println("Before block")
block()
println("After block")
}
fun main() {
inlineFunction {
println("Inside block")
// return // 编译错误,不能从 crossinline 的 Lambda 表达式中非局部返回
}
println("This will be printed")
}
2.5 尾递归函数
尾递归函数使用 tailrec 关键字优化递归调用,防止栈溢出。详见:高阶函数-尾递归优化
2.6 匿名函数
匿名函数类似于普通函数,但没有函数名。匿名函数可以指定返回类型,并且可以在函数体中使用 return 语句。
val sum = fun(x: Int, y: Int): Int {
return x + y
}
println(sum(2, 3)) // 输出 5
三、Lambda 表达式
3.1 基本语法
Lambda 表达式的基本语法如下:
val lambda: (Int, Int) -> Int = { x, y -> x + y }
-
Lambda 表达式由花括号
{}包围。 -
参数在箭头
->之前声明。 -
表达式在箭头
->之后。
3.2 单个参数的隐式名称 it
如果 Lambda 表达式只有一个参数,可以使用隐式名称 it 来引用该参数:
val numbers = listOf(1, 2, 3, 4, 5)
val doubled = numbers.map { it * 2 }
println(doubled) // 输出 [2, 4, 6, 8, 10]
3.3 多行 Lambda 表达式
Lambda 表达式可以包含多行代码,在这种情况下,最后一行代码是返回值:
val sum = { x: Int, y: Int ->
println("Adding $x and $y")
x + y
}
println(sum(2, 3)) // 输出 "Adding 2 and 3" 和 5
3.4 Lambda 表达式作为参数
Lambda 表达式可以作为函数参数传递:
fun performOperation(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
val result = performOperation(5, 3) { x, y -> x + y }
println(result) // 输出 8
3.5 返回 Lambda 表达式的函数
可以定义返回 Lambda 表达式的函数:
fun getOperation(): (Int, Int) -> Int {
return { x, y -> x + y }
}
val operation = getOperation()
println(operation(5, 3)) // 输出 8
3.6 带接收者的 Lambda 表达式
Kotlin 支持带接收者的 Lambda 表达式,可以用来模拟扩展函数。接收者作为 Lambda 表达式的隐式参数,可以直接调用接收者对象的成员:
val stringAppender: StringBuilder.() -> Unit = {
this.append("Hello ")
append("World!")
}
val stringBuilder = StringBuilder()
stringBuilder.stringAppender()
println(stringBuilder.toString()) // 输出 "Hello World!"
3.7 闭包
Lambda 表达式可以捕获并访问其所在环境中的变量,这种行为称为闭包
var sum = 0
val numbers = listOf(1, 2, 3, 4, 5)
numbers.forEach { sum += it }
println(sum) // 输出 15
3.8 匿名函数与Lambda 表达式
Lambda 表达式是匿名函数类型的实例,它与匿名函数有如下区别
-
语法:
- Lambda 表达式更简洁,适合短小的代码段。
- 匿名函数语法更接近普通函数,适合需要显式返回类型和
return语句的场景。
-
返回:
- 在 Lambda 表达式中,
return语句通常用于非局部返回,从包含它的函数返回。
fun foo() { println("Starting foo") listOf(1, 2, 3, 4, 5).forEach { if (it == 3) return // 非局部返回,直接返回到 foo 函数 println(it) } println("This line will not be printed") } fun main() { foo() // 输出 "Starting foo", "1", "2" println("End of main") }- 在匿名函数中,
return语句只返回到匿名函数内部,不会影响外部函数。
- 在 Lambda 表达式中,
-
类型推断:
- Lambda 表达式的参数类型通常由上下文推断。
- 匿名函数的参数类型需要显式声明。
四、类成员(成员方法、成员变量)
4.1 属性(成员变量)
- 基本属性声明:Kotlin 中的属性可以使用
val或var关键字声明,分别表示不可变和可变属性。class Person { val name: String = "John" // 不可变属性 var age: Int = 30 // 可变属性 } - 延迟初始化属性:对于不能立即初始化的属性,可以使用
lateinit关键字进行延迟初始化。这只能用于var属性,并且不能用于原始类型(如Int、Float)。class Person { lateinit var name: String fun initializeName(name: String) { this.name = name } fun printName() { if (this::name.isInitialized) { println(name) } else { println("Name is not initialized") } } } - 自定义 getter 和 setter:可以自定义属性的 getter 和 setter 方法
class Person { var age: Int = 0 get() = field set(value) { if (value >= 0) { field = value } else { println("Age cannot be negative") } } } - 惰性初始化:使用
lazy委托属性进行惰性初始化。lazy只适用于val属性,并且只能在第一次访问时初始化。class Person { val name: String by lazy { println("Initializing name") "John Doe" } }
4.2 可见性修饰符
Kotlin 支持四种可见性修饰符:public、private、protected 和 internal。
public:默认修饰符,任何地方都可以访问。private:仅在类内部可见。protected:在类及其子类中可见。internal:在同一模块中可见。
4.3 扩展函数
扩展函数允许向现有类添加新功能,而不需要继承或使用装饰器模式。
fun Person.sayGoodbye() {
println("Goodbye!")
}
fun main() {
val person = Person()
person.sayGoodbye() // 扩展函数调用
}
4.4 初始化块
init 块用于初始化类中的属性。它会在主构造函数之后执行。
class Person(val name: String, var age: Int) {
init {
println("Person initialized with name: $name and age: $age")
}
}
4.5 次构造函数
次构造函数使用 constructor 关键字声明,可以有多个次构造函数,并且它们需要调用主构造函数。
class Person(val name: String) {
var age: Int = 0
constructor(name: String, age: Int) : this(name) {
this.age = age
}
}
4.6 数据类
数据类用于仅包含数据的类,并自动生成常用方法,如 toString()、equals()、hashCode() 和 copy()。
data class User(val name: String, val age: Int)
fun main() {
val user1 = User("Alice", 30)
val user2 = user1.copy(name = "Bob")
println(user1) // 输出 "User(name=Alice, age=30)"
println(user2) // 输出 "User(name=Bob, age=30)"
}
五、基本运算
5.1 算术运算
Kotlin 支持常见的算术运算符
val a = 10
val b = 20
// 加法
val sum = a + b
// 减法
val difference = a - b
// 乘法
val product = a * b
// 除法
val quotient = b / a
// 取模(余数)
val remainder = b % a
println("sum: $sum, difference: $difference, product: $product, quotient: $quotient, remainder: $remainder")
// 输出:sum: 30, difference: -10, product: 200, quotient: 2, remainder: 0
5.2 比较运算
Kotlin 支持常见的比较运算符:
val x = 10
val y = 20
// 相等
val isEqual = (x == y)
// 不相等
val isNotEqual = (x != y)
// 大于
val isGreater = (x > y)
// 小于
val isLess = (x < y)
// 大于或等于
val isGreaterOrEqual = (x >= y)
// 小于或等于
val isLessOrEqual = (x <= y)
println("isEqual: $isEqual, isNotEqual: $isNotEqual, isGreater: $isGreater, isLess: $isLess, isGreaterOrEqual: $isGreaterOrEqual, isLessOrEqual: $isLessOrEqual")
// 输出:isEqual: false, isNotEqual: true, isGreater: false, isLess: true, isGreaterOrEqual: false, isLessOrEqual: true
5.3 逻辑运算
Kotlin 支持常见的逻辑运算符:
val a = true
val b = false
// 逻辑与
val and = a && b
// 逻辑或
val or = a || b
// 逻辑非
val notA = !a
println("and: $and, or: $or, notA: $notA")
// 输出:and: false, or: true, notA: false
5.4 位运算
Kotlin 支持位运算符,主要用于整数类型:
val a = 2 // 二进制:10
val b = 3 // 二进制:11
// 按位与
val and = a and b // 结果:2(10)
// 按位或
val or = a or b // 结果:3(11)
// 按位异或
val xor = a xor b // 结果:1(01)
// 按位取反
val inv = a.inv() // 结果:-3(按位取反后得到的补码)
// 左移
val shl = a shl 1 // 结果:4(100)
// 右移
val shr = a shr 1 // 结果:1(1)
// 无符号右移
val ushr = a ushr 1 // 结果:1(1)
println("and: $and, or: $or, xor: $xor, inv: $inv, shl: $shl, shr: $shr, ushr: $ushr")
// 输出:and: 2, or: 3, xor: 1, inv: -3, shl: 4, shr: 1, ushr: 1
5.5 赋值运算
Kotlin 支持常见的赋值运算符:
var x = 10
// 加法赋值
x += 5 // x = x + 5
// 减法赋值
x -= 3 // x = x - 3
// 乘法赋值
x *= 2 // x = x * 2
// 除法赋值
x /= 4 // x = x / 4
// 取模赋值
x %= 3 // x = x % 3
println("x: $x")
// 输出:x: 1
5.6 范围运算符
Kotlin 提供了简洁的范围运算符:
fun main() {
// 使用 `..` 运算符创建一个闭区间的范围(包含范围的结束值)。
val inclusiveRange = 1..5
println("Inclusive Range:")
for (i in inclusiveRange) {
println(i) // 输出 1, 2, 3, 4, 5
}
// 使用 `until` 运算符创建一个开区间的范围(不包含范围的结束值)。
val exclusiveRange = 1 until 5
println("Exclusive Range:")
for (i in exclusiveRange) {
println(i) // 输出 1, 2, 3, 4
}
// 使用 `downTo` 运算符创建一个降序的范围。
val descendingRange = 5 downTo 1
println("Descending Range:")
for (i in descendingRange) {
println(i) // 输出 5, 4, 3, 2, 1
}
// 使用 `step` 函数指定范围的步长。
val stepRange = 1..10 step 2
println("Step Range:")
for (i in stepRange) {
println(i) // 输出 1, 3, 5, 7, 9
}
}
5.7 其他常用运算符
5.7.1 安全调用运算符
用于处理可空类型的安全调用:
val str: String? = null
val length = str?.length // 如果 str 不为 null,则返回其长度,否则返回 null
println("length: $length") // 输出:length: null
5.7.2 Elvis 运算符
用于为可空类型提供默认值:
val str: String? = null
val length = str?.length ?: -1 // 如果 str 不为 null,则返回其长度,否则返回 -1
println("length: $length") // 输出:length: -1
5.7.3 非空断言运算符
如果你确定某个可空值不为 null,可以使用非空断言运算符 !!:
val str: String? = "Hello"
val length = str!!.length // 如果 str 为 null,会抛出 NullPointerException
println("length: $length") // 输出:length: 5
5.8 重载运算符(自定义运算符)
5.8.1 基本使用
以下是一些常见的可重载运算符及其对应的函数名称:
+对应plus-对应minus*对应times/对应div%对应rem..对应rangeTo[]对应get和setin对应contains
重载 + 运算符
首先,我们定义一个向量类,然后重载 + 运算符来实现向量相加:
data class Vector(val x: Int, val y: Int) {
operator fun plus(other: Vector): Vector {
return Vector(x + other.x, y + other.y)
}
}
fun main() {
val v1 = Vector(1, 2)
val v2 = Vector(3, 4)
val v3 = v1 + v2
println(v3) // 输出 Vector(x=4, y=6)
}
重载 .. 运算符
接下来,我们定义一个范围类,并重载 .. 运算符来创建范围:
class MyRange(val start: Int, val end: Int)
operator fun Int.rangeTo(other: Int) = MyRange(this, other)
fun main() {
val myRange = 1..5
println("Start: ${myRange.start}, End: ${myRange.end}") // 输出 Start: 1, End: 5
}
重载 get 和 set 运算符
我们定义一个包含整数数组的 Box 类,并重载 get 和 set 运算符,使其可以通过索引访问和修改数组中的元素:
fun main() {
val box = Box(5)
// 使用 set 运算符设置数组元素
box[0] = 1
box[1] = 2
box[2] = 3
box[3] = 4
box[4] = 5
// 使用 get 运算符获取数组元素
for (i in 0 until box.size) {
print("box[$i]=${box[i]} ")
//输出 box[0]=2 box[1]=3 box[2]=4 box[3]=5 box[4]=6
}
}
class Box(val size: Int) {
private val elements = IntArray(size)
// 重载 get 运算符
operator fun get(index: Int): Int {
return elements[index]
}
// 重载 set 运算符
operator fun set(index: Int, value: Int) {
elements[index] = value + 1
}
}
重载 in 运算符
我们可以重载 in 运算符来实现自定义包含检查。例如,定义一个区间类并重载 contains 函数
class MyRange(val start: Int, val end: Int) {
operator fun contains(value: Int): Boolean {
return value in start..end
}
}
fun main() {
val myRange = MyRange(1, 5)
println(3 in myRange) // 输出 true
println(6 in myRange) // 输出 false
}
5.8.2 规则和限制
-
重载运算符只能在类或对象的成员函数中实现。
-
重载运算符的函数必须使用
operator修饰符。 -
重载运算符的函数名称必须与相应的运算符函数名称匹配。
六、表达式(中缀表达式、分支表达式、when表达式)
6.1 中缀表达式
中缀表达式是一种简洁的调用方法的方式,使用 infix 关键字定义。它们通常用于表示具有自然语言风格的操作。
定义中缀函数
要定义一个中缀函数,需要满足以下条件:
- 必须是成员函数或扩展函数
- 必须只有一个参数
- 必须使用
infix关键字修饰
class Person(val name: String) {
infix fun likes(other: Person): Boolean {
return this.name == other.name
}
}
fun main() {
val alice = Person("Alice")
val bob = Person("Bob")
// 使用中缀表达式
val result = alice likes bob
println(result) // 输出 false
}
6.2 分支表达式
Kotlin 的 if 表达式不仅可以作为条件语句使用,还可以作为表达式返回值。
if 表达式
fun max(a: Int, b: Int): Int {
return if (a > b) {
a
} else {
b
}
}
fun main() {
val maxValue = max(10, 20)
println(maxValue) // 输出 20
}
单行 if 表达式
fun max(a: Int, b: Int): Int = if (a > b) a else b
6.3 when 表达式
when 表达式是 Kotlin 中用于多分支选择的结构,功能类似于其他语言的 switch 语句。when 表达式可以返回一个值。
6.3.1 基本用法
fun describe(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
fun main() {
println(describe(1)) // 输出 "One"
println(describe("Hello")) // 输出 "Greeting"
println(describe(1000L)) // 输出 "Long"
println(describe(2)) // 输出 "Unknown"
}
6.3.2 when 表达式作为返回值
when 表达式可以作为返回值,赋值给变量:
fun getColorName(color: String): String {
return when (color) {
"Red" -> "Color is Red"
"Green" -> "Color is Green"
"Blue" -> "Color is Blue"
else -> "Unknown Color"
}
}
fun main() {
val color = getColorName("Red")
println(color) // 输出 "Color is Red"
}
6.3.3 组合条件
在 when 表达式中,可以使用逗号组合多个条件:
fun getNumberDescription(number: Int): String {
return when (number) {
1, 2, 3 -> "Number is 1, 2, or 3"
4, 5, 6 -> "Number is 4, 5, or 6"
else -> "Unknown Number"
}
}
fun main() {
println(getNumberDescription(2)) // 输出 "Number is 1, 2, or 3"
}
6.3.4 区间和集合
when 表达式可以检查数值是否在某个区间或集合内:
fun getNumberCategory(number: Int): String {
return when (number) {
in 1..10 -> "Number is between 1 and 10"
in 11..20 -> "Number is between 11 and 20"
else -> "Number is out of range"
}
}
fun main() {
println(getNumberCategory(5)) // 输出 "Number is between 1 and 10"
println(getNumberCategory(15)) // 输出 "Number is between 11 and 20"
println(getNumberCategory(25)) // 输出 "Number is out of range"
}
6.3.5 带参数的 when
when 表达式可以根据任意条件进行分支,而不需要使用输入参数:
fun getDayType(day: String): String {
return when {
day.equals("Saturday", ignoreCase = true) -> "Weekend"
day.equals("Sunday", ignoreCase = true) -> "Weekend"
else -> "Weekday"
}
}
fun main() {
println(getDayType("Saturday")) // 输出 "Weekend"
println(getDayType("Monday")) // 输出 "Weekday"
}
七、循环语句(for循环、while循环、continue、break)
7.1 for 循环
Kotlin 的 for 循环用于遍历集合、区间或数组。for 循环的语法非常简洁,可以使用 in 关键字来遍历元素。
7.1.1 基本用法
//遍历集合:
val items = listOf("apple", "banana", "kiwi")
for (item in items) {
println(item)
}
// 输出:
// apple
// banana
// kiwi
//遍历区间:
for (i in 1..5) {
println(i)
}
// 输出:
// 1
// 2
// 3
// 4
// 5
//遍历数组:
val array = arrayOf(1, 2, 3, 4, 5)
for (i in array) {
println(i)
}
// 输出:
// 1
// 2
// 3
// 4
// 5
7.1.2 遍历带索引的集合
使用 withIndex 遍历集合时,可以同时获取元素和索引:
val items = listOf("apple", "banana", "kiwi")
for ((index, item) in items.withIndex()) {
println("Item at $index is $item")
}
// 输出:
// Item at 0 is apple
// Item at 1 is banana
// Item at 2 is kiwi
7.2 while 循环
while 循环在给定条件为 true 时重复执行循环体。条件在每次迭代前检查。
var x = 5
while (x > 0) {
println(x)
x--
}
// 输出:
// 5
// 4
// 3
// 2
// 1
7.3 do-while 循环
do-while 循环类似于 while 循环,但它先执行循环体,然后检查条件。因此,无论条件是否为 true,循环体至少会执行一次。
var y = 5
do {
println(y)
y--
} while (y > 0)
// 输出:
// 5
// 4
// 3
// 2
// 1
7.4 continue 和 break
continue 和 break 用于控制循环的执行。continue 跳过当前迭代并继续下一次迭代,而 break 则终止循环。
7.4.1 continue
for (i in 1..5) {
if (i == 3) continue // 跳过当前迭代
println(i)
}
// 输出:
// 1
// 2
// 4
// 5
7.4.2 break
for (i in 1..5) {
if (i == 3) break // 终止循环
println(i)
}
// 输出:
// 1
// 2
7.4.3 标签(Label)
Kotlin 支持为循环加标签,允许在多重循环中使用 break 和 continue 跳出或继续指定的循环。
outer@ for (i in 1..5) {
for (j in 1..5) {
if (j == 3) continue@outer // 跳过外层循环的当前迭代
println("i: $i, j: $j")
}
}
// 输出:
// i: 1, j: 1
// i: 1, j: 2
// i: 2, j: 1
// i: 2, j: 2
// i: 3, j: 1
// i: 3, j: 2
// i: 4, j: 1
// i: 4, j: 2
// i: 5, j: 1
// i: 5, j: 2
7.5 循环的常见用法
7.5.1 使用 step 控制步长
通过 step 函数可以指定循环的步长:
for (i in 1..10 step 2) {
println(i)
}
// 输出:
// 1
// 3
// 5
// 7
// 9
集合使用 filterIndexed 实现 step 功能
val items = listOf("apple", "banana", "kiwi", "orange", "grape")
val steppedItems = items.filterIndexed { index, _ -> index % 2 == 0 }
for (item in steppedItems) {
println(item)
}
// 输出:apple, kiwi, grape
7.5.2 使用 downTo 实现倒序循环
通过 downTo 函数可以实现倒序循环:
for (i in 10 downTo 1) {
println(i)
}
// 输出:
// 10
// 9
// 8
// 7
// 6
// 5
// 4
// 3
// 2
// 1
集合通过 reversed 可以实现集合的逆序
val items = listOf("apple", "banana", "kiwi", "orange", "grape")
val reversedItems = items.reversed()
for (item in reversedItems) {
println(item)
}
// 输出:grape, orange, kiwi, banana, apple
八、异常捕获
8.1 非受检异常(Unchecked Exceptions)
与 Java 不同,Kotlin 没有受检异常(Checked Exceptions)。在 Kotlin 中,所有异常都是非受检异常,这意味着函数不需要声明它们可能抛出的异常,调用者也不需要强制捕获这些异常。
fun riskyOperation() {
throw Exception("Something went wrong")
}
fun main() {
try {
riskyOperation()
} catch (e: Exception) {
println("Caught an exception: ${e.message}")
}
}
在这个示例中,riskyOperation 函数抛出异常,但不需要在函数签名中声明 throws,调用者也不需要强制捕获异常。
8.2 try 表达式
Kotlin 的 try 块不仅可以用于捕获异常,还可以作为表达式返回值。这使得在异常处理中返回值变得更加简洁和自然。
fun parseInt(str: String): Int? {
return try {
str.toInt()
} catch (e: NumberFormatException) {
null
}
}
fun main() {
val number = parseInt("123") ?: 0
println("Parsed number: $number") // 输出:Parsed number: 123
val invalidNumber = parseInt("abc") ?: 0
println("Parsed number: $invalidNumber") // 输出:Parsed number: 0
}
在这个示例中,try 表达式的结果可以直接作为返回值,这在处理需要返回默认值的场景中特别有用。
8.3 runCatching 函数
Kotlin 提供了 runCatching 函数,它可以简化异常捕获的流程,并返回一个封装了结果的对象(Result),从而避免显式的 try-catch 块。
fun riskyOperation(): Int {
throw Exception("Something went wrong")
}
fun main() {
val result = runCatching { riskyOperation() }
println(result.isFailure) // 输出:true
val value = result.getOrElse { 0 }
println("Result: $value") // 输出:Result: 0
}
8.3.1 扩展函数 getOrElse 和 getOrNull
Result 类提供了扩展函数 getOrElse 和 getOrNull,用于在处理异常时提供默认值或返回 null。
fun riskyOperation(): Int {
throw Exception("Something went wrong")
}
fun main() {
val result = runCatching { riskyOperation() }
// 使用 getOrElse 提供默认值
val value = result.getOrElse { 0 }
println("Result: $value") // 输出:Result: 0
// 使用 getOrNull 返回 null
val nullableValue = result.getOrNull()
println("Nullable Result: $nullableValue") // 输出:Nullable Result: null
}
8.3.2 扩展函数 onFailure 和 onSuccess
Result 类还提供了扩展函数 onFailure 和 onSuccess,用于分别处理成功和失败的情况。
fun riskyOperation(): Int {
throw Exception("Something went wrong")
}
fun safeOperation(): Int {
return 42
}
fun main() {
val result = runCatching { riskyOperation() }
result.onFailure { exception ->
println("Caught an exception: ${exception.message}")
}.onSuccess { value ->
println("Operation successful, result: $value")
}
val safeResult = runCatching { safeOperation() }
safeResult.onFailure { exception ->
println("Caught an exception: ${exception.message}")
}.onSuccess { value ->
println("Operation successful, result: $value")
}
}
九、具名参数、变长参数、默认参数
9.1 具名参数
具名参数(Named Parameters)允许在调用函数时明确指定参数名称,使代码更加清晰和易读。具名参数可以与位置参数混合使用,但具名参数必须在位置参数之后。
fun displayDetails(name: String, age: Int, city: String) {
println("Name: $name, Age: $age, City: $city")
}
fun main() {
// 使用位置参数调用函数
displayDetails("John", 25, "New York")
// 使用具名参数调用函数
displayDetails(name = "Alice", age = 30, city = "Los Angeles")
// 混合使用位置参数和具名参数
displayDetails("Bob", city = "Chicago", age = 28)
}
在这个示例中,使用具名参数使得代码更加清晰易读,特别是在参数较多时。
9.2 变长参数
变长参数(Vararg Parameters)允许传递可变数量的参数。使用 vararg 关键字声明变长参数。变长参数在函数内部作为数组处理。
fun printAll(vararg items: String) {
for (item in items) {
println(item)
}
}
fun main() {
// 传递单个参数
printAll("apple")
// 传递多个参数
printAll("banana", "kiwi", "orange")
// 传递数组
val fruits = arrayOf("grape", "pineapple")
printAll(*fruits) // 使用展开操作符
}
在这个示例中,printAll 函数可以接受任意数量的字符串参数,并逐个打印。
9.3 默认参数
默认参数(Default Parameters)允许为函数参数提供默认值。在调用函数时,如果没有传递相应的参数,就使用默认值。默认参数使得函数调用更加灵活。
fun greet(name: String = "Guest") {
println("Hello, $name")
}
fun main() {
// 使用默认参数
greet() // 输出 "Hello, Guest"
// 覆盖默认参数
greet("John") // 输出 "Hello, John"
}
在这个示例中,greet 函数的 name 参数有一个默认值 "Guest"。
9.4 综合示例
fun showInfo(name: String, age: Int = 18, vararg hobbies: String) {
println("Name: $name, Age: $age")
print("Hobbies: ")
for (hobby in hobbies) {
print("$hobby ")
}
println()
}
fun main() {
// 使用默认参数和变长参数
showInfo("Alice")
// 覆盖默认参数
showInfo("Bob", 25)
// 使用变长参数
showInfo("Charlie", hobbies = *arrayOf("Reading", "Swimming"))
// 使用具名参数和变长参数
showInfo(name = "Dave", hobbies = "Gaming", "Traveling")
}
在这个综合示例中:
showInfo函数的age参数有默认值18。hobbies参数使用vararg关键字,可以接受任意数量的字符串参数。- 函数调用可以使用具名参数,使代码更具可读性。