Skip to content

变量、类型与空安全

源:Kotlin 基础语法参考

Kotlin 的类型系统旨在提供 编译时确定性,同时通过强大的 类型推导 (Type Inference) 保持代码的简洁与优雅。

变量声明 (val 与 var)

Kotlin 强制区分 只读引用可变引用,这有助于构建线程安全和易于维护的代码。

kotlin
// 对应 Java 的 final
// 必须在执行期间初始化一次
val pi: Double = 3.14159
val name = "Kotlin" // 自动推导为 String
// name = "Java" // ❌ 编译错误:Val cannot be reassigned
kotlin
// 普通变量,可多次赋值
var score = 0 
score = 100
score += 50

核心基础类型

在 Kotlin 中,一切皆对象。你可以在任何变量上调用成员函数与属性。

数字类型 (Numbers)

Kotlin 处理数字时不允许隐式拓宽转换 (Implicit Widening Conversions),这与 Java 不同(如 Java 中 int 可自动转 long)。

kotlin
val i: Int = 10
// val l: Long = i // ❌ 编译错误:Type mismatch
val l: Long = i.toLong() // ✅ 必须显式转换

无符号整型 (Unsigned Integers)

从 Kotlin 1.5 开始稳定支持。适用于底层二进制操作或对接 C/C++ API。

kotlin
val uByte: UByte = 255u
val uInt: UInt = 1234u
val uLong: ULong = 1234uL

字符串 (Strings)

字符串是不可变的。Kotlin 提供了模板语法来简化字符串拼接。

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 编译器非常聪明。如果你检查了类型,编译器会自动在后续分支中将变量视为该类型,无需强转。

kotlin
fun demo(x: Any) {
    if (x is String) {
        // x 在此作用域内自动转换为 String
        print(x.length) 
    }
}

安全转换 (Safe Cast)

使用 as? 避免转换异常 (ClassCastException)。

kotlin
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)

不引入新类型,仅为现有类型提供更短或更有意义的名称。

kotlin
// 1. 缩短过长的泛型类型
typealias NodeSet = Set<Network.Node>

// 2. 语义化函数类型
typealias ClickHandler = (View, Boolean) -> Unit

fun setHandler(handler: ClickHandler) { /*...*/ }

核心:空安全系统 (Null Safety)

Kotlin 的类型系统在编译期区分 可空 (Nullable)不可空 (Non-nullable) 类型,从根源上消灭 NullPointerException

类型体系

kotlin
var a: String = "abc"
// a = null // ❌ 编译错误
val len = a.length // ✅ 总是安全的
kotlin
var b: String? = "abc"
b = null // ✅ 允许
// val len = b.length // ❌ 编译错误:可能为空

安全操作符

  1. 安全调用 (?.)

    kotlin
    val length: Int? = b?.length // 如果 b 为 null,返回 null
  2. Elvis 操作符 (?:) 常用于提供默认值或提前返回。

    kotlin
    val len = b?.length ?: 0 // 如果左侧 null,返回 0
    
    // 配合 return/throw 使用(因为 throw 是表达式)
    val name = node.getName() ?: throw IllegalArgumentException("Name required")
  3. 非空断言 (!!) 转换为非空类型,虽然简单,但如果是 null 会抛出 NPE。仅在 100% 确定不为空时使用

    kotlin
    val len = b!!.length

延迟初始化

特性lateinitlazy
修饰符varval
类型限制非空引用类型,不能是基本类型 (Int/Long)无限制
原理编译期不做检查,运行时若未初始化则抛异常使用委托对象,首次访问时计算
线程安全不保证默认线程安全 (SYNCHRONIZED)
kotlin
// 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 是所有类型的子类型。这意味着它可以替代任何类型。

kotlin
// 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> 的子类型,这防止了运行时类型错误。

kotlin
// 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
// Java
public class User {
    // 没有注解,可能是 null
    public String getNickname() { return null; }
}
kotlin
// Kotlin
val user = User()
val name = user.nickname // 类型是 String!

// 危险!编译器允许这样做,但运行时会崩
print(name.length) // 💥 NullPointerException

最佳实践: 调用 Java 代码时,总是显式声明类型

  • 如果你认为它不为空:val name: String = user.nickname (会在赋值时立即检查,空则崩在赋值处,更容易排查)。
  • 如果你认为它可能为空:val name: String? = user.nickname (强制后续做空检查)。