news 2026/8/25 16:35:21

Kotlin 语言【知识点整理2】

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Kotlin 语言【知识点整理2】

目录

一、基本概念

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 = 1

1.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.7182818284

1.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 是表达式,因此它天然就会产生值,无论是否被接,比如下面的表达式:
    1. 有变量接受,返回值就有意义。所以,当需要返回值时,必须显式指定接收位置
    2. 没有变量接受,返回值就没有意义(直接丢弃)
// 作为表达式返回最大值 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 的值是 30

2.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: Orange

2.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: n

2.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 }
  1. 参数格式:参数名: 类型
  2. 返回值类型在参数后声明(如 : 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` 访问接收者对象(可省略) }
  1. ReceiverType: 要扩展的类(接收者类型)
  2. functionName: 扩展函数的名称
  3. 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

  1. 表示它返回一个函数
  2. 这个返回的函数:接受一个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,则使用右侧默认值
  1. 先计算 ?: 左侧表达式
  2. 如果结果非空 -> 直接返回该值
  3. 如果结果为 null -> 返回右侧表达式结果
  • 非空断言操作符

这个慎用,因为会导致空指针​:告诉编译器,我确定这个值不会为null,哪怕真的为null,你也可以直接抛空指针

val length: Int = text!!.length // 断言 text 不为 null,若为 null 则立即抛 NPE

2.为什么需要这些符号?

通过类型系统强制开发者显式处理空值,将运行时可能出现的 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 交互的代码中,使用 !! 前务必确认数据源的非空性
  • 永远不要为了"快速完成代码"而滥用 !!
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/25 16:31:01

一台新服务器上线 Java 服务,我会检查这 20 项

一台新服务器上线 Java 服务&#xff0c;我会检查这 20 项JDK装好了&#xff0c;JAR也能启动。 这台服务器就可以上生产了吗&#xff1f;不一定。 很多线上事故和业务代码没有关系&#xff1a; 服务用root运行 服务器重启后应用没有自动启动 时区错误导致定时任务提前执行 文件…

作者头像 李华
网站建设 2026/8/25 16:30:32

Live2D模型制作全流程实战:从PSD到可驱动模型的避坑指南

1. 这篇文章真正要解决的问题如果你是一名开发者、内容创作者&#xff0c;或者对数字人、虚拟形象技术感兴趣&#xff0c;最近可能被各种“AI数字人”、“虚拟主播”刷屏。但当你真正想动手做一个属于自己的、能实时互动的Live2D模型时&#xff0c;面对的却是一堆陌生的名词&am…

作者头像 李华
网站建设 2026/8/25 16:25:03

ArcGIS Pro空间数据预处理实战:从地图到机器学习特征表的完整流程

1. 先搞清楚“空间数据预处理”到底要解决什么问题如果你正在用 ArcGIS Pro 做机器学习&#xff0c;卡住你的往往不是模型本身&#xff0c;而是第一步&#xff1a;怎么把地图数据变成模型能“吃”的格式。很多人一上来就找算法、调参数&#xff0c;结果模型跑不起来&#xff0c…

作者头像 李华
网站建设 2026/8/25 16:24:24

MySQL InnoDB 引擎中的聚簇索引和非聚簇索引有什么区别?

聚簇索引和非聚簇索引的根本区别在于&#xff1a;数据存储方式和物理顺序。一、核心概念 1. 聚簇索引&#xff08;Clustered Index&#xff09; 聚簇索引是指索引的叶子节点直接存储了整行数据。在 InnoDB 中&#xff0c;主键就是聚簇索引。 2. 非聚簇索引&#xff08;Non-Clus…

作者头像 李华
网站建设 2026/8/25 16:18:53

基于本地大模型的Markdown转LaTeX自动化方案:从原理到工程实践

这次我们来看一个非常实用的本地大模型应用场景&#xff1a;将 Markdown 文档自动转换为 LaTeX 源码。对于需要撰写学术论文、技术报告或书籍的作者来说&#xff0c;在 Markdown 的便捷书写和 LaTeX 的精美排版之间反复手动转换&#xff0c;是一项耗时且容易出错的工作。借助本…

作者头像 李华
网站建设 2026/8/25 16:00:35

指针(4):C 语言数组与指针深度剖析

指针&#xff08;4&#xff09;&#xff1a;C 语言数组与指针深度剖析前言1. 数组名1.1 数组名的理解1.2 例外1.2.1 sizeof&#xff08;数组名&#xff09;1.2.2 &数组名1.2.3 总结2.使用指针访问数组3.一维数组传参本质总结前言 很多人学 C 语言&#xff0c;数组和指针总…

作者头像 李华