变量、类型与空安全
Kotlin 的类型系统旨在提供 编译时确定性,同时通过强大的 类型推导 (Type Inference) 保持代码的简洁与优雅。
变量声明 (val 与 var)
Kotlin 强制区分 只读引用 与 可变引用,这有助于构建线程安全和易于维护的代码。
// 对应 Java 的 final
// 必须在执行期间初始化一次
val pi: Double = 3.14159
val name = "Kotlin" // 自动推导为 String
// name = "Java" // ❌ 编译错误:Val cannot be reassigned// 普通变量,可多次赋值
var score = 0
score = 100
score += 50核心基础类型
在 Kotlin 中,一切皆对象。你可以在任何变量上调用成员函数与属性。
数字类型 (Numbers)
Kotlin 处理数字时不允许隐式拓宽转换 (Implicit Widening Conversions),这与 Java 不同(如 Java 中 int 可自动转 long)。
val i: Int = 10
// val l: Long = i // ❌ 编译错误:Type mismatch
val l: Long = i.toLong() // ✅ 必须显式转换无符号整型 (Unsigned Integers)
从 Kotlin 1.5 开始稳定支持。适用于底层二进制操作或对接 C/C++ API。
val uByte: UByte = 255u
val uInt: UInt = 1234u
val uLong: ULong = 1234uL字符串 (Strings)
字符串是不可变的。Kotlin 提供了模板语法来简化字符串拼接。
val name = "Kotlin"
// 1. 字符串模板
println("Hello, $name! Length: ${name.length}")
// 2. 原始字符串 (Raw String)
// 使用三引号,包含换行符和任意字符,常用于 SQL 或 JSON
val sql = """
SELECT * FROM Users
WHERE name = '$name'
""".trimIndent()类型检查与转换 (Casts)
智能转换 (Smart Casts)
Kotlin 编译器非常聪明。如果你检查了类型,编译器会自动在后续分支中将变量视为该类型,无需强转。
fun demo(x: Any) {
if (x is String) {
// x 在此作用域内自动转换为 String
print(x.length)
}
}安全转换 (Safe Cast)
使用 as? 避免转换异常 (ClassCastException)。
val y: Any = "123"
val z: Int? = y as? Int // 转换失败返回 null,而不是抛出异常位运算 (Bitwise)
Kotlin 使用 中缀函数 (Infix functions) 代替特殊的位运算符,语义更清晰。
| 操作 | 中缀函数 | Java 对应 | 示例 |
|---|---|---|---|
| 左移 | shl | << | 1 shl 2 (4) |
| 右移 | shr | >> | 4 shr 1 (2) |
| 无符号右移 | ushr | >>> | -1 ushr 1 |
| 按位与 | and | & | x and y |
| 按位或 | or | | | x or y |
| 按位异或 | xor | ^ | x xor y |
| 按位取反 | inv() | ~ | x.inv() |
类型别名 (Typealias)
不引入新类型,仅为现有类型提供更短或更有意义的名称。
// 1. 缩短过长的泛型类型
typealias NodeSet = Set<Network.Node>
// 2. 语义化函数类型
typealias ClickHandler = (View, Boolean) -> Unit
fun setHandler(handler: ClickHandler) { /*...*/ }核心:空安全系统 (Null Safety)
Kotlin 的类型系统在编译期区分 可空 (Nullable) 与 不可空 (Non-nullable) 类型,从根源上消灭 NullPointerException。
类型体系
var a: String = "abc"
// a = null // ❌ 编译错误
val len = a.length // ✅ 总是安全的var b: String? = "abc"
b = null // ✅ 允许
// val len = b.length // ❌ 编译错误:可能为空安全操作符
安全调用 (
?.)kotlinval length: Int? = b?.length // 如果 b 为 null,返回 nullElvis 操作符 (
?:) 常用于提供默认值或提前返回。kotlinval len = b?.length ?: 0 // 如果左侧 null,返回 0 // 配合 return/throw 使用(因为 throw 是表达式) val name = node.getName() ?: throw IllegalArgumentException("Name required")非空断言 (
!!) 转换为非空类型,虽然简单,但如果是 null 会抛出 NPE。仅在 100% 确定不为空时使用。kotlinval len = b!!.length
延迟初始化
| 特性 | lateinit | lazy |
|---|---|---|
| 修饰符 | var | val |
| 类型限制 | 非空引用类型,不能是基本类型 (Int/Long) | 无限制 |
| 原理 | 编译期不做检查,运行时若未初始化则抛异常 | 使用委托对象,首次访问时计算 |
| 线程安全 | 不保证 | 默认线程安全 (SYNCHRONIZED) |
// 1. by lazy: 首次使用时初始化 (适合单例或重资源)
val dbConnection by lazy { connectToDb() }
// 2. lateinit: 稍后初始化 (适合依赖注入或生命周期回调)
@Inject
lateinit var viewModel: UserViewModel
// 检查是否已初始化
if (::viewModel.isInitialized) { /*...*/ }根类型:Any, Unit, Nothing
| 类型 | 描述 | Java 对应 |
|---|---|---|
Any | 所有非空类型的超类 | Object |
Unit | 函数无返回值时的返回类型 (它是单例对象) | void |
Nothing | 没有实例,表示“永远不会返回” (如抛异常) | (无) |
Nothing 的实战价值
Nothing 是所有类型的子类型。这意味着它可以替代任何类型。
// fail() 返回 Nothing,所以它可以赋值给 String 类型的 name
val name: String = input ?: fail("Input required")
fun fail(msg: String): Nothing {
throw IllegalArgumentException(msg)
}数组 (Arrays)
Kotlin 数组是不变的 (Invariant)。这意味着 Array<String> 并不是 Array<Any> 的子类型,这防止了运行时类型错误。
// 1. 对象数组 (装箱,开销较大)
val strings = arrayOf("a", "b")
val numbers = Array(5) { i -> (i * i).toString() }
// 2. 原生类型数组 (无装箱,性能极佳)
val ints: IntArray = intArrayOf(1, 2, 3)
// 访问与修改
ints[0] = 5互操作风险:平台类型 (Platform Types)
当 Kotlin 调用 Java 代码时,如果 Java 类型没有 @Nullable 或 @NotNull 注解,Kotlin 无法确定其空安全性。此时它被视为 平台类型 (标记为 T!)。
隐患示例
// Java
public class User {
// 没有注解,可能是 null
public String getNickname() { return null; }
}// Kotlin
val user = User()
val name = user.nickname // 类型是 String!
// 危险!编译器允许这样做,但运行时会崩
print(name.length) // 💥 NullPointerException最佳实践: 调用 Java 代码时,总是显式声明类型。
- 如果你认为它不为空:
val name: String = user.nickname(会在赋值时立即检查,空则崩在赋值处,更容易排查)。 - 如果你认为它可能为空:
val name: String? = user.nickname(强制后续做空检查)。