目录
一、基本概念
1.包的定义与导入
2.程序入口点
2.1 输入
3.变量
二、基本类型
1.数字
1.1 整数类型
1.2 浮点类型
1.3 数字字面常量
1.4 装箱与缓存
1.4.1 JVM是怎么存储数字的?
1.4.2 使用可空类型的时候会触发装箱操作:
1.4.3 JVM 对 -128 到 127 的小数字做了特殊优化:
1.5 显式数字转换
案例1:整数与浮点数
案例2:数字超范围
1.6 数字运算
1.7 整数除法
2.布尔
3.字符
3.1 特殊字符
4.字符串
4.1 基础特性
4.2 常用操作
4.3 扩展函数增强
4.4 性能优化技巧
4.5 与Java互操作
三、控制语句
1.条件语句(if 与 when)
2.循环语句
2.1 遍历范围(Range)
2.2 遍历数组
2.3 遍历集合(List/Set)
2.4 遍历 Map
2.5 遍历字符串字符
2.6 自定义迭代器
2.7 控制流操作
2.8 标签控制
2.9 与 Java 对比
四、函数
1.函数的定义
2.扩展函数
2.1 使用案例
3.高阶函数
3.1 语法
3.2 使用案例
五、空安全机制
1.问号(?)的作用与用法
2.为什么需要这些符号?
3.最佳实践示例
3.1 安全处理空值
4.关键总结
一、基本概念
1.包的定义与导入
包的声明应处于源文件顶部:
package my.demo //包名 import kotlin.text.* //引用某个包2.程序入口点
Kotlin 应用程序的入口点是 main 函数:
fun main() { println("Hello world!") }main 的另一种形式接受可变数量的 String 参数,当程序需要处理命令行参数时可以使用这个形式,感兴趣可自行扩展(如读取文件名、配置选项等):
fun main(args: Array<String>) { println(args.contentToString()) }2.1 输入
// Prints a message to request input println("请输入: ") // 这里使用了一个不可变的youword参数,读取并存储用户输入, val yourWord = readln() // 显示你输入的信息 print("你输入了: ") print(yourWord)3.变量
kotlin具有强大的类型推断,我们可以让编译器通过推断变量的类型来完成工作。
//声明一个可变变量并初始化它 var a: String = "initial" println(a) //声明一个不可变变量并初始化它。 val b: Int = 1 //声明不可变变量并在不指定类型的情况下对其进行初始化。编译器推断Int类型。 val c = 3变量必须初始化后才可以使用,否则会报错,只要在第一次使用它之前做初始化就可以
//声明一个没有初始化的变量。 var e: Int //尝试使用该变量会导致编译器错误:变量‘e’必须初始化。 println(e) //初始化后再用 e = 666二、基本类型
Kotlin 中使用的类型:数字、布尔、字符、字符串、数组。
1.数字
1.1 整数类型
Kotlin 提供了一组表示数字的内置类型。 对于整数,有四种不同大小与取值范围的类型:
类型 | 大小(比特数) | 最小值 | 最大值 |
Byte | 8 | -128 | 127 |
Short | 16 | -32768 | 32767 |
Int | 32 | -2,147,483,648 | 2,147,483,647 |
Long | 64 | -9,223,372,036,854,775,808 | 9,223,372,036,854,775,807 |
当初始化一个没有显式指定类型的变量时,编译器会自动推断为自 Int 起足以表示该值的最小类型。 如果不超过 Int 的取值范围,那么类型是 Int。 如果超出了该范围,那么类型是 Long。 如需显式指定 Long 值,请给该值追加后缀 L。 显式指定类型会触发编译器检测该值不会超出指定类型的取值范围。
val one = 1 // Int val threeBillion = 3000000000 // Long val oneLong = 1L // Long val oneByte: Byte = 11.2 浮点类型
对于实数,Kotlin 提供了浮点类型Float单精度与Double双精度两种类型
类型 | 大小(比特数) | 10进制位数 | 最小值 | 最大值 |
Float | 32 | 6-7 | 3.4028235 × 10³⁸ | 1.7976931348623157 × 10³⁰⁸ |
Double | 64 | 15-16 | 1.4 × 10⁻⁴⁵ | 4.9 × 10⁻³²⁴ |
对于以小数初始化的变量,编译器会推断为 Double 类型:
val pi = 3.14 // Double val one: Double = 1 //Double val oneDouble = 1.0 // Double如需将一个值显式指定为 Float 类型,请添加 f 或 F 后缀。 如果以这种方式提供的值包含多于 7 位十进制数,那么会将其舍入:
val e = 2.7182818284 // Double val eFloat = 2.7182818284f // Float,实际值为 2.7182817与一些其他语言不同,Kotlin 中的数字没有隐式拓宽转换。 例如,具有 Double 参数的函数只能对 Double 值调用,而不能对 Float、 Int 或者其他数字值调用:
fun main() { //sampleStart fun printDouble(x: Double) { print(x) } val x = 1.0 val xInt = 1 val xFloat = 1.0f printDouble(x)//参数类型匹配 printDouble(xInt) // 参数类型不匹配 printDouble(xFloat) // 参数类型不匹配 //如果需要让xInt或者xFloat使用这个函数,需要先进行显式类型转换 xInt.toDouble() }1.3 数字字面常量
数值常量(直接在代码中表示固定值的符号)字面值有以下几种:
- 十进制:123
- Long 类型,以大写 L 结尾:123L
- 十六进制:0x0F
- 二进制:0b00001011
Kotlin 不支持八进制。
Kotlin 同样支持浮点数的常规表示方法:
- Doubles (default when the fractional part does not end with a letter): 123.5, 123.5e10
- Float,以字母 f 或 F 结尾:123.5f
你可以使用下划线使数字常量更易读:
val oneMillion = 1_000_000 //实际值仍然是 1000000(一百万),下划线 _ 作为视觉分隔符,使数字更易读.在编译阶段会被完全忽略,不影响实际值 val creditCardNumber = 1234_5678_9012_3456L val socialSecurityNumber = 999_99_9999L val hexBytes = 0xFF_EC_DE_5E val bytes = 0b11010010_01101001_10010100_10010010 val bigFractional = 1_234_567.71828182841.4 装箱与缓存
1.4.1 JVM是怎么存储数字的?
- 基本类型:int、double 等(直接存数值,高效)
- 装箱类型:Integer、Double 等(存对象,用于特殊场景)
1.4.2 使用可空类型的时候会触发装箱操作:
- Int? 中的问号(?)表示可空类型(Nullable Type),这是 Kotlin 类型系统中最重要的特性之一
- Kotlin 通过问号语法,强制开发者显式处理 null 的情况,避免 NullPointerException
val basic: Int = 10 // 基本类型(高效) 不可为 null 的整数(等价于 Java 的 int 基本类型) val boxed: Int? = 10 // 装箱类型(对象) 可以为 null 的整数(等价于 Java 的 Integer 包装类,但更安全)1.4.3 JVM 对 -128 到 127 的小数字做了特殊优化:
- 这些数字装箱后会共用同一个缓存对象
- 超出范围的数字每次装箱都会创建新对象
//假如是1个127以内的数,jvm会对其进行缓存,哪怕装箱以后,也都会指向同一个对象 val b: Int = 100 val boxedB: Int? = b // 装箱 val anotherBoxedB: Int? = b // 再次装箱 println(boxedB === anotherBoxedB) // 因为100没超出缓存范围,两个引用指向同一个对象 输出:true println(boxedB == anotherBoxedB) // == 比较的是数值内容,两者都是100 输出:true //但如果b是的范围不属于 -128 到 127,那么就不再是同一个对象 val b: Int = 10000 println(boxedB === anotherBoxedB) // 因为10000超出缓存范围,每次装箱都创建新对象 输出:false注意:这里只是借助?可空类型以及===来说明装箱后的缓存现象:
- 除非必要,不要用 Int?,一定是优先使用基本类型
- 始终使用 == 比较数字,而不是使用===比较内存地址,因为这似乎没啥意义
1.5 显式数字转换
(1)较小的类型不会隐式地转换为较大的类型,反之亦然。例如,将Byte类型的值赋给Int变量需要显式转换
fun main() { val byte: Byte = 1 val intAssignedByte: Int = byte //会报错 val intConvertedByte: Int = byte.toInt()//需要显示转换 println(intConvertedByte) }所有数字类型都支持转换为其他类型,只需要调用数字下的 toxxx函数方法:toByte()、toShort()、toInt()、toLong()、toFloat()、toDouble()。
(2) 也有很多不需要显式类型转换的情况,因为程序会根据上下文推断类型, 而且算术运算符也会有重载自动处理类型转换。例如:
fun main() { // 场景1:根据赋值自动推断 val name = "Kotlin" // 自动推断为 String val score = 95 // 自动推断为 Int val ratio = 0.9 // 自动推断为 Double //场景2:算术运算符重载处理 val l = 1L + 3 // Long + Int => 得到的是Long println(l is Long) // 使用is判断l的类型 输出:true }Kotlin 刻意设计为不支持隐式类型转换(Implicit Conversions),这与 Java、C++ 等语言不同。以下是关键原因和典型案例:
案例1:整数与浮点数
val price: Double = 10 // 编译错误!必须显式转换 // 正确做法: val correctPrice: Double = 10.toDouble()10 是 Int 类型,直接赋值给 Double 会导致精度意外丢失的风险。Kotlin 强制要求开发者明确转换意图。对比 Java 的隐式转换:
double price = 10; // Java 自动隐式转换(可能掩盖潜在问题)案例2:数字超范围
val bigNumber: Long = 10000000000 // 编译错误!超出 Int 范围 // 必须显式声明为 Long: val correctNumber = 10000000000L设计哲学:Kotlin 要求开发者明确知晓自己在处理大数字,避免数值溢出等隐蔽问题。
1.6 数字运算
Kotlin支持数字运算的标准集:+、 -、 *、 /、 %
fun main() { println(1 + 2) println(2_500_000_000L - 1L) println(3.14 * 2.71) println(10.0 / 3) }1.7 整数除法
整数间的除法总是返回整数。会丢弃任何小数部分。
fun main() { val x = 5 / 2 println(x == 2.5) //会提示:操作符‘==’不能应用于‘Int’和‘Double’ println(x == 2) // 只保留整数部分 输出:true }对于任何两个整数类型之间的除法来说都是如此:
fun main() { val x = 5L / 2 println (x == 2) //提示错误,因为x此时是Long类型,因为不能将Long (x)与Int(2)进行比较 println(x == 2L) // true }如需返回带小数部分的除法结果,请将其中的一个参数显式转换为浮点类型:
fun main() { val x = 5 / 2.toDouble() //这种做法会让x被推测为Double类型 println(x == 2.5) }2.布尔
Boolean 类型表示可以有 true 与 false 两个值的布尔对象
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 }||和&&操作符是惰性的,这意味着:
- 如果第一个操作数为真,||运算符不计算第二个操作数。
- 如果第一个操作数为假,&&操作符不对第二个操作数求值。
3.字符
字符用 Char 类型表示。 字符字面值用单引号括起来: '1'
3.1 特殊字符
特殊字符可以以转义反斜杠 \开始。 支持这几个转义序列:
- \t——制表符
- \b——退格符
- \n——换行(LF)
- \r——回车(CR)
- \'——单引号
- \"——双引号
- \\——反斜杠
- \$——美元符
- 如果需要编码其他字符,也可以用 Unicode 转义序列语法:'\uFF00'。
fun main() { val aChar: Char = 'a' println(aChar) println('\n') // 输出一个额外的换行符 println('\uFF00') }如果字符变量的值是数字,也可以使用 digitToInt() 函数将其显式转换为 Int 数字。
4.字符串
Kotlin 中字符串用 String 类型表示,既保留了 Java 的强大功能,又通过扩展函数和语言特性大幅提升了易用性
4.1 基础特性
(1)不可变性:与 Java 相同,Kotlin 字符串是不可变的
val s = "hello" s[0] = 'H' // 编译错误!String不可变(2)两种字面量
- 转义字符串:支持转义字符
val path = "C:\\Windows\\System32"- 原始字符串(Raw String):三引号包裹,保留所有格式
val json = """ { "name": "Kotlin", "age": 10 } """.trimIndent()4.2 常用操作
- 比 Java 更强大的插值功能
val name = "Alice" val greeting = "Hello, $name!" // 插入简单变量 val lengthInfo = "Name length: ${name.length}" // 插入表达式 println("1 + 2 = ${1 + 2}") // 输出:1 + 2 = 3- 拼接与重复
val combined = "Hello" + " " + "World" // 传统方式 val repeated = "Na".repeat(3) + " Batman!" // 输出:NaNaNa Batman!- 多行字符串处理
val poem = """ |床前明月光, |疑是地上霜。 |举头望明月, |低头思故乡。 """.trimMargin("|") // 自动去除前导|和缩进- 分割与连接
val langs = "Kotlin,Java,Swift".split(",") // 拆分为List val merged = langs.joinToString(" | ") // 合并为"Kotlin | Java | Swift"4.3 扩展函数增强
除了常用操作以外,Kotlin 为字符串添加了大量实用扩展(展示部分,感兴趣可以查API文档或者问AI):
函数 | 作用 | 示例 |
substringBefore/After | 截取子串 | "file.txt".substringBefore(".") → "file" |
padStart/padEnd | 填充对齐 | "5".padStart(2, '0') → "05" |
toIntOrNull | 安全转换 | "123".toIntOrNull() → 123 |
isBlank | 空值检查 | " ".isBlank() → true |
replace | 正则替换 | "123-456".replace(Regex("\\D"), "") → "123456" |
4.4 性能优化技巧
- 字符串构建器
val sb = buildString { append("Hello") append(" ") append("World") } // 比连续"+"拼接效率更高- 避免重复创建
// 错误做法(每次循环创建新对象) var result = "" for (i in 1..1000) { result += i.toString() } // 正确做法 val betterResult = StringBuilder().apply { for (i in 1..1000) { append(i) } }.toString()4.5 与Java互操作
- 无缝调用Java字符串方法
val javaStyle = "Kotlin".toLowerCase(Locale.ROOT)- 消除空指针隐患
val maybeString: String? = getJavaString() val safeLength = maybeString?.length ?: 0 // 安全访问 //输出结果如下: //当getJavaString() 返回 null 时 → safeLength = 0 //当返回空串"" 时 → safeLength = 0 //当返回有效字符串时 →safeLength = 字符串长度三、控制语句
kotlin拥有比Java更智能的流程控制
1.条件语句(if 与 when)
Kotlin 的if 不仅是语句,还可以是表达式(有返回值),返回值由每个分支的最后一行的表达式决定:
- 注意,这种表达式型的写法, if 必须有 else 分支,否则会报错
- Kotlin 的 if 是表达式,因此它天然就会产生值,无论是否被接,比如下面的表达式:
- 有变量接受,返回值就有意义。所以,当需要返回值时,必须显式指定接收位置
- 没有变量接受,返回值就没有意义(直接丢弃)
// 作为表达式返回最大值 val max = if (a > b) { println("a 更大") a // 最后一行的表达式会自动成为该分支的返回值 } else { b } // 替代 Java 的三元运算符, val status = if (score >= 60) "通过" else "未通过"when是增强版switch,支持任意类型和复杂条件:
//常量值匹配,类似于switch when (status) { 200 -> println("成功") 404 -> println("未找到") 500 -> println("服务器错误") else -> println("未知状态") } //类型检查、智能转换:使用 `is` 或 `!is` 来检查对象的类型,并且在分支内自动智能转换为该类型 when (shape) { is Circle -> println("半径: ${shape.radius}") // 自动转为 Circle is Square -> println("边长: ${shape.side}") // 自动转为 Square !is Int -> println("不是整数") } //范围匹配 when (score) { in 90..100 -> println("A") in 80..89 -> println("B") !in 60..100 -> println("不及格") else -> println("未知类型") } // 作为表达式使用,直接输出结果,并且使用变量接收 val description = when { temperature > 30 -> "炎热" temperature < 10 -> "寒冷" else -> "舒适" }2.循环语句
kotlin的循环语句简介且强大
2.1 遍历范围(Range)
// 基础、常规 for (i in 1..5) { print("$i ") // 输出: 1 2 3 4 5 } // 反向 for (i in 5 downTo 1) { print("$i ") // 输出: 5 4 3 2 1 } // 带步长 for (i in 1..10 step 2) { print("$i ") // 输出: 1 3 5 7 9 } // 半开范围(不包含结束值) for (i in 1 until 5) { print("$i ") // 输出: 1 2 3 4 }2.2 遍历数组
val numbers = arrayOf(10, 20, 30) // 遍历元素 for (num in numbers) { print("$num ") // 输出: 10 20 30 } // 带索引遍历 for (index in numbers.indices) { println("索引 $index 的值是 ${numbers[index]}") } // 输出: // 索引 0 的值是 10 // 索引 1 的值是 20 // 索引 2 的值是 302.3 遍历集合(List/Set)
val fruits = listOf("Apple", "Banana", "Orange") // 基础遍历 for (fruit in fruits) { print("$fruit ") // 输出: Apple Banana Orange } // 带索引遍历 //val iterable = fruits.withIndex() 是kotlin当中的一个可迭代对象 //当我们在for循环中使用了iterable对象,实际上是发生了下面这几件事: 1、隐式调用iterable.iterator(),内部会产生index 2、循环开始时:检查iterator.hasNext() 3、每次迭代:获取item = iterator.next() for ((index, fruit) in fruits.withIndex()) { println("$index: $fruit") } // 输出: // 0: Apple // 1: Banana // 2: Orange2.4 遍历 Map
val map = mapOf("a" to 1, "b" to 2, "c" to 3) // 遍历键值对 for ((key, value) in map) { println("$key -> $value") } // 输出: // a -> 1 // b -> 2 // c -> 3 // 仅遍历键 for (key in map.keys) { print("$key ") // 输出: a b c } // 仅遍历值 for (value in map.values) { print("$value ") // 输出: 1 2 3 }2.5 遍历字符串字符
val text = "Kotlin" // 遍历字符 for (char in text) { print("$char-") // 输出: K-o-t-l-i-n- } // 带索引遍历 for ((i, char) in text.withIndex()) { println("$i: $char") } // 输出: // 0: K // 1: o // 2: t // 3: l // 4: i // 5: n2.6 自定义迭代器
系统提供的功能能应对多数场景,但kotlin也提供了自定义迭代器,实现Iterable接口的类可被遍历,对自定义迭代器感兴趣,后续可自行扩展学习
//定义了一个名为 Counter 的自定义迭代器类,它实现了 Iterable<Int> 接口 class Counter(val max: Int) : Iterable<Int> { override fun iterator(): Iterator<Int> = object : Iterator<Int> { private var current = 0 // 当前计数值 // 检查是否还有下一个值 override fun hasNext() = current < max // 获取下一个值并移动 override fun next() = current++ } } // 使用时,指定max的值是3,然后自定义迭代器就会根据你定义的条件进行遍历 for (i in Counter(3)) { print("$i ") // 输出: 0 1 2 }2.7 控制流操作
使用continue、break进行流程控制
// 跳过当前迭代(continue) for (i in 1..5) { if (i == 3) continue print("$i ") // 输出: 1 2 4 5 } // 中断循环(break) for (i in 1..10) { if (i > 5) break print("$i ") // 输出: 1 2 3 4 5 } // 标签跳转(控制外层循环) outer@ for (i in 1..3) { for (j in 1..3) { if (i == 2 && j == 2) break@outer print("($i,$j) ") } } // 输出: (1,1) (1,2) (1,3) (2,1)2.8 标签控制
精准控制循环跳转:
outer@ for (i in 1..3) { inner@ for (j in 1..3) { if (i == 2 && j == 2) break@outer print("($i,$j) ") // 输出: (1,1) (1,2) (1,3) (2,1) } }2.9 与 Java 对比
特性 | Java | Kotlin | 优势 |
条件返回值 | 三元运算符 a>b ? a : b | if (a>b) a else b | 支持多行逻辑 |
多分支条件 | switch-case | when 表达式 | 支持任意类型和复杂条件 |
范围循环 | for (int i=0; i<10; i++) | for (i in 0 until 10) | 避免索引越界错误 |
四、函数
1.函数的定义
- 函数声明使用fun关键字定义:
fun add(a: Int, b: Int): Int { return a + b }
- 参数格式:参数名: 类型
- 返回值类型在参数后声明(如 : Int)
- 表达式函数(单表达式)
省略 {} 和 return:
fun add(a: Int, b: Int): Int = a + b // 类型可自动推断,进一步简化: fun add(a: Int, b: Int) = a + b参数支持默认值:
fun greet(name: String = "Guest") { println("Hello, $name!") }调用:greet() 输出 Hello, Guest!
调用时,明确参数名:
fun createUser(name: String, age: Int, isAdmin: Boolean = false) { ... } // 指定参数名(可乱序) createUser(age = 25, name = "Alice")可变参数,类似 Java 的 ...
fun sum(vararg numbers: Int): Int { return numbers.sum() } sum(1, 2, 3) // 输出 6顶层函数,可脱离类直接定义在文件中:
// File: MathUtils.kt fun multiply(a: Int, b: Int) = a * b无需像 Java 的静态工具类。
局部函数,内部嵌套函数:
fun outer() { fun inner() { println("Inner") } inner() }2.扩展函数
扩展函数(Extension Functions) 是一种在不修改原类定义的情况下,为现有类添加新功能的机制。它们通过静态解析实现,不会侵入原类代码,但能像类成员一样被调用。
- 语法结构
fun ReceiverType.functionName(params): ReturnType { // 函数体 // 通过 `this` 访问接收者对象(可省略) }
- ReceiverType: 要扩展的类(接收者类型)
- functionName: 扩展函数的名称
- this: 在函数体内指向接收者对象实例(隐式可用)
- 本质是扩展函数是静态解析的静态函数(编译时确定),不会修改原类,而是通过编译器转换为静态方法调用。例如:
// 编译后实际会生成: fun String.lastChar() → public static char lastChar(String receiver)2.1 使用案例
- 为String扩展一个函数,获取o在字符串中第一次出现的位置
/** * 获取o在字符串中第一次出现的位置,如果没出现就返回-1 */ fun String.getOIndex(): Int { // 函数体 // 如果在这里面调用this,`this` 指代调用该方法的字符串对象,另外,this也可以被省略 return this.indexOf("o") } //调用 val s :String= "hello" val oIndex = s.getOIndex() println("o出现的位置是:$oIndex")- View扩展:简化 Android 中的视图控制
fun View.show() { this.visibility = View.VISIBLE } fun View.hide() { this.visibility = View.GONE } // 使用 myButton.show() // 等价于 myButton.visibility = View.VISIBLE- 可空类型扩展:安全处理空值
fun String?.safeLength(): Int { return this?.length ?: 0 // 若为 null 返回 0 } fun main() { val s: String? = null println(s.safeLength()) // 输出:0(不会抛 NPE) }3.高阶函数
高阶函数 是指可以接受函数作为参数、或者返回一个函数的函数。将行为作为参数传递,使代码更灵活且可复用,同时保持类型安全。极大地增强了代码的抽象能力和灵活性。
3.1 语法
// 定义高阶函数 fun 函数名(参数名: (参数类型) -> 返回类型) { // 调用传入的函数 参数名(实参) }3.2 使用案例
- 基础回调函数,把函数当成参数传递使用
// 定义高阶函数 fun performRequest( url: String, onSuccess: (response: String) -> Unit, // 函数类型参数,Unit表示函数不返回任何有意义的值,类似于java的void onFailure: (exception: Exception) -> Unit // 函数类型参数 ) { try { // 模拟网络请求 val response = "响应数据: $url" onSuccess(response) // 调用成功回调 } catch (e: Exception) { onFailure(e) // 调用失败回调 } } // 使用 fun main() { performRequest( url = "https://api.example.com", onSuccess = { response -> println("成功: $response") }, onFailure = { error -> println("失败: ${error.message}") } ) }- 带返回值的函数参数
// 定义高阶函数 fun transformData( data: List<Int>, transform: (Int) -> String // 接收Int返回String的函数 ): List<String> { return data.map(transform) // 使用传入的转换函数 } // 使用 fun main() { val numbers = listOf(1, 2, 3) val result = transformData(numbers) { num -> "数字: ${num * 2}" // Lambda 表达式 } println(result) // 输出: [数字: 2, 数字: 4, 数字: 6] }- 返回函数的函数
// 定义高阶函数(返回一个函数) fun createLogger(prefix: String): (String) -> Unit { return { message -> // 返回一个Lambda表达式 println("[$prefix] $message") } } // 使用 fun main() { val activityLogger = createLogger("MainActivity") val networkLogger = createLogger("Network") activityLogger("Activity 已创建") // 输出: [MainActivity] Activity 已创建 networkLogger("请求发送") // 输出: [Network] 请求发送 }返回类型:(String) -> Unit
- 表示它返回一个函数
- 这个返回的函数:接受一个String参数,返回Unit
五、空安全机制
在 Kotlin 中,? 和 !!都是空安全机制的核心组成部分,用于处理可能为 null 的值。这是 Kotlin 区别于 Java 的重要特性,能有效减少 NullPointerException(空指针异常)。
1.问号(?)的作用与用法
- 声明可空类型
var name: String? = null // 声明一个可能为 null 的字符串不加 ? 的类型默认为非空类型(不能赋 null 值):
var age: Int = null // 编译错误:Null can not be a value of a non-null type Int- 安全调用操作符(?.)
val length: Int? = text?.length // 如果 text 为 null,返回 null 而不报错等价于 Java 的:
Integer length = text != null ? text.length() : null;安全转换(as?)
安全的类型转换,失败时返回 null 而非抛异常,避免出现避免 ClassCastException
val num: Int? = obj as? Int // 转换失败时返回 null 而不是抛异常Elvis 操作符(?:)
左侧表达式为 null 时可以提供一个默认值,简化空值处理
val name = user?.name ?: "Unknown" // 若左边为 null,则使用右侧默认值
- 先计算 ?: 左侧表达式
- 如果结果非空 -> 直接返回该值
- 如果结果为 null -> 返回右侧表达式结果
非空断言操作符
这个慎用,因为会导致空指针:告诉编译器,我确定这个值不会为null,哪怕真的为null,你也可以直接抛空指针
val length: Int = text!!.length // 断言 text 不为 null,若为 null 则立即抛 NPE2.为什么需要这些符号?
通过类型系统强制开发者显式处理空值,将运行时可能出现的 NPE 转为编译期错
场景 | Java | Kotlin(安全) | Kotlin(危险) |
访问可能为 null 的对象 | text.length()→ 可能 NPE | text?.length→ 返回 null | text!!.length→ 可能 NPE |
优势 | 编译时不检查空值 | 编译时强制处理空值 | 开发者主动承担风险 |
3.最佳实践示例
3.1 安全处理空值
// 链式安全调用 + Elvis 操作符 val cityName = user?.address?.city ?: "China" // 配合 let 作用域函数 //为对象创建一个临时作用域,在内部安全操作非空对象 user?.let { println("Name: ${it.name}") // 仅在非 null 时执行 }4.关键总结
操作符 | 含义 | 风险程度 |
? | "这可能为 null" | 无风险 |
?. | "为 null 时跳过" | 无风险 |
?: | "为 null 时使用默认值" | 无风险 |
!! | "我保证这里不为 null!" | 高风险 |
终极建议:
- 90% 的场景使用 ?. 和 ?: 替代 !!
- 剩余 10% 与 Java 交互的代码中,使用 !! 前务必确认数据源的非空性
- 永远不要为了"快速完成代码"而滥用 !!