Kotlin 语言【知识点整理2】

目录

一、基本概念

1.包的定义与导入

2.程序入口点

[2.1 输入](#2.1 输入)

3.变量

二、基本类型

1.数字

[1.1 整数类型](#1.1 整数类型)

[1.2 浮点类型](#1.2 浮点类型)

[1.3 数字字面常量](#1.3 数字字面常量)

[1.4 装箱与缓存](#1.4 装箱与缓存)

[1.4.1 JVM是怎么存储数字的?](#1.4.1 JVM是怎么存储数字的?)

[1.4.2 使用可空类型的时候会触发装箱操作:](#1.4.2 使用可空类型的时候会触发装箱操作:)

[1.4.3 JVM 对 -128 到 127 的小数字做了特殊优化:](#1.4.3 JVM 对 -128 到 127 的小数字做了特殊优化:)

[1.5 显式数字转换](#1.5 显式数字转换)

案例1:整数与浮点数

案例2:数字超范围

[1.6 数字运算](#1.6 数字运算)

[1.7 整数除法](#1.7 整数除法)

2.布尔

3.字符

[3.1 特殊字符](#3.1 特殊字符)

4.字符串

[4.1 基础特性](#4.1 基础特性)

[4.2 常用操作](#4.2 常用操作)

[4.3 扩展函数增强](#4.3 扩展函数增强)

[4.4 性能优化技巧](#4.4 性能优化技巧)

[4.5 与Java互操作](#4.5 与Java互操作)

三、控制语句

[1.条件语句(if 与 when)](#1.条件语句(if 与 when))

2.循环语句

[2.1 遍历范围(Range)](#2.1 遍历范围(Range))

[2.2 遍历数组](#2.2 遍历数组)

[2.3 遍历集合(List/Set)](#2.3 遍历集合(List/Set))

[2.4 遍历 Map](#2.4 遍历 Map)

[2.5 遍历字符串字符](#2.5 遍历字符串字符)

[2.6 自定义迭代器](#2.6 自定义迭代器)

[2.7 控制流操作](#2.7 控制流操作)

[2.8 标签控制](#2.8 标签控制)

[2.9 与 Java 对比](#2.9 与 Java 对比)

四、函数

1.函数的定义

2.扩展函数

[2.1 使用案例](#2.1 使用案例)

3.高阶函数

[3.1 语法](#3.1 语法)

[3.2 使用案例](#3.2 使用案例)

五、空安全机制

1.问号(?)的作用与用法

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

3.最佳实践示例

[3.1 安全处理空值](#3.1 安全处理空值)

4.关键总结


一、基本概念

1.包的定义与导入

包的声明应处于源文件顶部:

Kotlin 复制代码
package my.demo //包名

import kotlin.text.* //引用某个包

2.程序入口点

Kotlin 应用程序的入口点是 main 函数:

Kotlin 复制代码
fun main() {
    println("Hello world!")
}

main 的另一种形式接受可变数量的 String 参数,当程序需要处理命令行参数时可以使用这个形式,感兴趣可自行扩展(如读取文件名、配置选项等):

Kotlin 复制代码
fun main(args: Array<String>) {
    println(args.contentToString())
}

2.1 输入

Kotlin 复制代码
// Prints a message to request input
println("请输入: ")

// 这里使用了一个不可变的youword参数,读取并存储用户输入,
val yourWord = readln()

// 显示你输入的信息
print("你输入了: ")
print(yourWord)

3.变量

kotlin具有强大的类型推断,我们可以让编译器通过推断变量的类型来完成工作。

Kotlin 复制代码
//声明一个可变变量并初始化它
var a: String = "initial"
println(a)
//声明一个不可变变量并初始化它。
val b: Int = 1 
//声明不可变变量并在不指定类型的情况下对其进行初始化。编译器推断Int类型。
val c = 3 

变量必须初始化后才可以使用,否则会报错,只要在第一次使用它之前做初始化就可以

Kotlin 复制代码
//声明一个没有初始化的变量。
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。 显式指定类型会触发编译器检测该值不会超出指定类型的取值范围。

Kotlin 复制代码
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 类型:

Kotlin 复制代码
val pi = 3.14          // Double

val one: Double = 1    //Double

val oneDouble = 1.0    // Double

如需将一个值显式指定为 Float 类型,请添加 f 或 F 后缀。 如果以这种方式提供的值包含多于 7 位十进制数,那么会将其舍入:

Kotlin 复制代码
val e = 2.7182818284          // Double
val eFloat = 2.7182818284f    // Float,实际值为 2.7182817

与一些其他语言不同,Kotlin 中的数字没有隐式拓宽转换。 例如,具有 Double 参数的函数只能对 Double 值调用,而不能对 Float、 Int 或者其他数字值调用:

Kotlin 复制代码
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

你可以使用下划线使数字常量更易读:

Kotlin 复制代码
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
Kotlin 复制代码
val basic: Int = 10 
// 基本类型(高效) 不可为 null 的整数(等价于 Java 的 int 基本类型) 


val boxed: Int? = 10 
// 装箱类型(对象) 可以为 null 的整数(等价于 Java 的 Integer 包装类,但更安全)
1.4.3 JVM 对 -128 到 127 的小数字做了特殊优化:
  • 这些数字装箱后会共用同一个缓存对象
  • 超出范围的数字每次装箱都会创建新对象
Kotlin 复制代码
//假如是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变量需要显式转换

Kotlin 复制代码
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) 也有很多不需要显式类型转换的情况,因为程序会根据上下文推断类型, 而且算术运算符也会有重载自动处理类型转换。例如:

Kotlin 复制代码
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:整数与浮点数
Kotlin 复制代码
val price: Double = 10  // 编译错误!必须显式转换
// 正确做法:
val correctPrice: Double = 10.toDouble()

10 是 Int 类型,直接赋值给 Double 会导致精度意外丢失的风险。Kotlin 强制要求开发者明确转换意图。对比 Java 的隐式转换:

java 复制代码
double price = 10;  // Java 自动隐式转换(可能掩盖潜在问题)
案例2:数字超范围
Kotlin 复制代码
val bigNumber: Long = 10000000000  // 编译错误!超出 Int 范围
// 必须显式声明为 Long:
val correctNumber = 10000000000L

设计哲学 :Kotlin 要求开发者明确知晓自己在处理大数字,避免数值溢出等隐蔽问题。

1.6 数字运算

Kotlin支持数字运算的标准集:+、 -、 *、 /、 %

Kotlin 复制代码
fun main() {
    println(1 + 2)
    println(2_500_000_000L - 1L)
    println(3.14 * 2.71)
    println(10.0 / 3)
}

1.7 整数除法

整数间的除法总是返回整数。会丢弃任何小数部分。

Kotlin 复制代码
fun main() {
    val x = 5 / 2 
    println(x == 2.5) //会提示:操作符'=='不能应用于'Int'和'Double'
    println(x == 2)    // 只保留整数部分 输出:true
}

对于任何两个整数类型之间的除法来说都是如此:

Kotlin 复制代码
fun main() {
    val x = 5L / 2
    println (x == 2) //提示错误,因为x此时是Long类型,因为不能将Long (x)与Int(2)进行比较
    println(x == 2L) // true
}

需返回带小数部分的除法结果,请将其中的一个参数显式转换为浮点类型:

Kotlin 复制代码
fun main() {
    val x = 5 / 2.toDouble() //这种做法会让x被推测为Double类型
    println(x == 2.5)
}

2.布尔

Boolean 类型表示可以有 true 与 false 两个值的布尔对象

Kotlin 复制代码
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'。
Kotlin 复制代码
fun main() {
    val aChar: Char = 'a'

    println(aChar)
    println('\n') // 输出一个额外的换行符
    println('\uFF00')
}

如果字符变量的值是数字,也可以使用 digitToInt() 函数将其显式转换为 Int 数字。

4.字符串

Kotlin 中字符串用 String 类型表示,既保留了 Java 的强大功能,又通过扩展函数和语言特性大幅提升了易用性

4.1 基础特性

(1) 不可变性:与 Java 相同,Kotlin 字符串是不可变的

Kotlin 复制代码
val s = "hello"
s[0] = 'H'  // 编译错误!String不可变

(2) 两种字面量

  • 转义字符串:支持转义字符
Kotlin 复制代码
val path = "C:\\Windows\\System32"
  • 原始字符串(Raw String):三引号包裹,保留所有格式
Kotlin 复制代码
val json = """
    {
      "name": "Kotlin",
      "age": 10
    }
""".trimIndent()

4.2 常用操作

  • 比 Java 更强大的插值功能
Kotlin 复制代码
val name = "Alice"
val greeting = "Hello, $name!"  // 插入简单变量
val lengthInfo = "Name length: ${name.length}"  // 插入表达式
println("1 + 2 = ${1 + 2}")  // 输出:1 + 2 = 3
  • 拼接与重复
Kotlin 复制代码
val combined = "Hello" + " " + "World"  // 传统方式
val repeated = "Na".repeat(3) + " Batman!"  // 输出:NaNaNa Batman!
  • 多行字符串处理
Kotlin 复制代码
val poem = """
    |床前明月光,
    |疑是地上霜。
    |举头望明月,
    |低头思故乡。
""".trimMargin("|")  // 自动去除前导|和缩进
  • 分割与连接
Kotlin 复制代码
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 性能优化技巧

  • 字符串构建器
Kotlin 复制代码
val sb = buildString {
    append("Hello")
    append(" ")
    append("World")
}
// 比连续"+"拼接效率更高
  • 避免重复创建
Kotlin 复制代码
// 错误做法(每次循环创建新对象)
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字符串方法
Kotlin 复制代码
val javaStyle = "Kotlin".toLowerCase(Locale.ROOT)
  • 消除空指针隐患
Kotlin 复制代码
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. 没有变量接受,返回值就没有意义(直接丢弃)
Kotlin 复制代码
// 作为表达式返回最大值
val max = if (a > b) {
    println("a 更大")
    a  // 最后一行的表达式会自动成为该分支的返回值
} else {
    b
}

// 替代 Java 的三元运算符,
val status = if (score >= 60) "通过" else "未通过"

when 是增强版 switch,支持任意类型和复杂条件:

Kotlin 复制代码
//常量值匹配,类似于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)

Kotlin 复制代码
// 基础、常规
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 遍历数组

Kotlin 复制代码
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)

Kotlin 复制代码
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

Kotlin 复制代码
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 遍历字符串字符

Kotlin 复制代码
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接口的类可被遍历,对自定义迭代器感兴趣,后续可自行扩展学习

Kotlin 复制代码
//定义了一个名为 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进行流程控制

Kotlin 复制代码
// 跳过当前迭代(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 标签控制

精准控制循环跳转:

Kotlin 复制代码
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 关键字定义:
Kotlin 复制代码
fun add(a: Int, b: Int): Int {
    return a + b
}
  1. 参数格式:参数名: 类型
  2. 返回值类型在参数后声明(如 : Int)
  • 表达式函数(单表达式)

省略 {} 和 return:

Kotlin 复制代码
fun add(a: Int, b: Int): Int = a + b
// 类型可自动推断,进一步简化:
fun add(a: Int, b: Int) = a + b
  • 参数支持默认值:
Kotlin 复制代码
fun greet(name: String = "Guest") {
    println("Hello, $name!")
}

调用:greet() 输出 Hello, Guest!

  • 调用时,明确参数名:
Kotlin 复制代码
fun createUser(name: String, age: Int, isAdmin: Boolean = false) { ... }
// 指定参数名(可乱序)
createUser(age = 25, name = "Alice")
  • 可变参数​​,类似 Java 的 ...
Kotlin 复制代码
fun sum(vararg numbers: Int): Int {
    return numbers.sum()
}
sum(1, 2, 3) // 输出 6
  • 顶层函数​​,可脱离类直接定义在文件中:
Kotlin 复制代码
// File: MathUtils.kt
fun multiply(a: Int, b: Int) = a * b

无需像 Java 的静态工具类。

  • 局部函数,内部嵌套函数:
Kotlin 复制代码
fun outer() {
    fun inner() { println("Inner") }
    inner()
}

2.扩展函数

扩展函数(Extension Functions)​​ 是一种在不修改原类定义的情况下,为现有类添加新功能的机制。它们通过静态解析实现,不会侵入原类代码,但能像类成员一样被调用。

  • 语法结构
Kotlin 复制代码
fun ReceiverType.functionName(params): ReturnType {
    // 函数体
    // 通过 `this` 访问接收者对象(可省略)
}
  1. ReceiverType: 要扩展的类(接收者类型)
  2. functionName: 扩展函数的名称
  3. this: 在函数体内指向接收者对象实例(隐式可用)
  • 本质是扩展函数是静态解析的静态函数(编译时确定),不会修改原类,而是通过编译器转换为静态方法调用。例如:
Kotlin 复制代码
// 编译后实际会生成:
fun String.lastChar() → public static char lastChar(String receiver)

2.1 使用案例

  • 为String扩展一个函数,获取o在字符串中第一次出现的位置
Kotlin 复制代码
/**
 * 获取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 中的视图控制
Kotlin 复制代码
fun View.show() {
    this.visibility = View.VISIBLE
}

fun View.hide() {
    this.visibility = View.GONE
}

// 使用
myButton.show() // 等价于 myButton.visibility = View.VISIBLE
  • 可空类型扩展:安全处理空值
Kotlin 复制代码
fun String?.safeLength(): Int {
    return this?.length ?: 0 // 若为 null 返回 0
}

fun main() {
    val s: String? = null
    println(s.safeLength()) // 输出:0(不会抛 NPE)
}

3.高阶函数

高阶函数 是指可以接受函数作为参数、或者返回一个函数的函数。将行为作为参数传递,使代码更灵活且可复用,同时保持类型安全。极大地增强了代码的抽象能力和灵活性。

3.1 语法

Kotlin 复制代码
// 定义高阶函数
fun 函数名(参数名: (参数类型) -> 返回类型) {
    // 调用传入的函数
    参数名(实参)
}

3.2 使用案例

  • 基础回调函数,把函数当成参数传递使用
Kotlin 复制代码
// 定义高阶函数
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}")
        }
    )
}
  • 带返回值的函数参数
Kotlin 复制代码
// 定义高阶函数
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]
}
  • 返回函数的函数
Kotlin 复制代码
// 定义高阶函数(返回一个函数)
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.问号(?)的作用与用法

  • 声明可空类型
Kotlin 复制代码
var name: String? = null  // 声明一个可能为 null 的字符串

不加 ? 的类型默认为​​非空类型​​(不能赋 null 值):

Kotlin 复制代码
var age: Int = null // 编译错误:Null can not be a value of a non-null type Int
  • 安全调用操作符(?.)
Kotlin 复制代码
val length: Int? = text?.length  // 如果 text 为 null,返回 null 而不报错

等价于 Java 的:

java 复制代码
Integer length = text != null ? text.length() : null;
  • 安全转换(as?)

安全的类型转换,失败时返回 null 而非抛异常,避免出现避免 ClassCastException

Kotlin 复制代码
val num: Int? = obj as? Int  // 转换失败时返回 null 而不是抛异常
  • Elvis 操作符(?:)

左侧表达式为 null 时可以提供一个默认值,简化空值处理​

Kotlin 复制代码
val name = user?.name ?: "Unknown"  // 若左边为 null,则使用右侧默认值
  1. 先计算 ?: 左侧表达式
  2. 如果结果非空 -> 直接返回该值
  3. 如果结果为 null -> 返回右侧表达式结果
  • 非空断言操作符

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

Kotlin 复制代码
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 安全处理空值

Kotlin 复制代码
// 链式安全调用 + Elvis 操作符
val cityName = user?.address?.city ?: "China"

// 配合 let 作用域函数
//为对象创建一个临时作用域,在内部安全操作非空对象
user?.let { 
    println("Name: ${it.name}") // 仅在非 null 时执行
}

4.关键总结

|-----|---------------------------|------|
| 操作符 | 含义 | 风险程度 |
| ? | "这可能为 null" | 无风险 |
| ?. | "为 null 时跳过" | 无风险 |
| ?: | "为 null 时使用默认值" | 无风险 |
| !! | ​ ​"我保证这里不为 null!"​ ​ | 高风险 |

终极建议:

  • 90% 的场景使用 ?. 和 ?: 替代 !!
  • 剩余 10% 与 Java 交互的代码中,使用 !! 前务必确认数据源的非空性
  • 永远不要为了"快速完成代码"而滥用 !!
相关推荐
不会就选b1 小时前
Linux之线程池(一)
java·开发语言
福大大架构师每日一题1 小时前
webrtc-rs/webrtc v0.20.3更新:Android 网络切换后 ICE Restart 卡死约 10 秒的问题终于解决
android·网络·webrtc
牛油果子哥q1 小时前
C++序列式容器深度精讲:vector/list/deque底层实现、扩容原理、迭代器失效、性能对比、工程选型避坑
开发语言·c++·list
mmsx1 小时前
基于 Android 的本地商城订单 App:SQLite + 多角色架构的技术实践(有源码和文档)
android·架构·sqlite
渡我白衣1 小时前
Acceptor模块的设计与实现
java·linux·服务器·开发语言·网络·c++·人工智能
沫璃染墨2 小时前
《从零入门Linux系统篇(二十八):文件篇·一——Linux为什么“万物皆文件”:从C语言文件流到系统调用》
linux·运维·服务器·开发语言·c++·文件
luteres3 小时前
Mybatis+SSM整合笔记
java·开发语言·mybatis
郝学胜-神的一滴3 小时前
《C++11 工程级应用01:告别冗长类型,开启简洁高效编码新时代》深度解读
开发语言·c++·算法·软件开发·系统设计
其实防守也摸鱼3 小时前
推荐一个自动化教育SRC漏洞挖掘系统--AutoHunter
运维·开发语言·人工智能·学习·安全·web安全·自动化