作为一名有五年Kotlin开发经验的工程师,我经常看到新手在变量使用上踩坑。今天我们就来彻底搞懂Kotlin变量的那些事儿。变量是编程中最基础也最重要的概念之一,就像建筑工地上的钢筋水泥,用对了才能建出稳固的大楼。
变量本质上就是内存中的一块存储空间,它有三个核心属性:
在Kotlin中声明变量就像给仓库贴标签:
kotlin复制// 声明一个存放年龄的仓库
val age: Int = 25
这里:
age 是仓库标签(变量名)Int 规定只能存整数(数据类型)25 是实际存放的值(变量值)经验之谈:好的变量名应该像路标一样清晰,看到名字就知道用途。避免使用模糊的命名如
a、temp等。
Kotlin中变量声明有两种方式:
val(value的缩写):不可变引用var(variable的缩写):可变引用实际开发中,我遵循一个黄金法则:优先使用val。这能带来三大好处:
来看个电商场景的例子:
kotlin复制// 用户ID一旦生成就不该改变
val userId: String = "U10086"
// 购物车金额需要随时变动
var cartTotal: Double = 0.0
Kotlin的数据类型比Java更精细,主要分为以下几类:
| 类型分类 | 代表类型 | 存储大小 | 取值范围 | 典型用法 |
|---|---|---|---|---|
| 整数类型 | Byte | 1字节 | -128~127 | 小范围计数 |
| Short | 2字节 | -32768~32767 | 中等范围数值 | |
| Int | 4字节 | -2^31~2^31-1 | 最常用的整数类型 | |
| Long | 8字节 | -2^63~2^63-1 | 大数值如时间戳 | |
| 浮点类型 | Float | 4字节 | 约±3.4E38 | 一般精度小数 |
| Double | 8字节 | 约±1.7E308 | 高精度计算 | |
| 布尔类型 | Boolean | 1位 | true/false | 条件判断 |
| 字符类型 | Char | 2字节 | Unicode字符 | 单个字符处理 |
| 字符串类型 | String | 可变 | 任意文本 | 文本处理 |
Kotlin有强大的类型推断能力,这让代码更简洁:
kotlin复制// 显式声明
val name: String = "Kotlin"
// 类型推断(推荐)
val name = "Kotlin" // 自动推断为String
但要注意几个特殊情况:
kotlin复制val price = 9.99 // Double类型
val floatPrice = 9.99f // 要Float必须加f后缀
kotlin复制val bigNumber = 10000000000L // Long类型
Kotlin最伟大的特性之一就是空安全。通过在类型后加?表示可空:
kotlin复制var nullableName: String? = null // 允许为null
var nonNullName: String = "Kotlin" // 不能为null
处理可空变量的正确姿势:
kotlin复制// 安全调用操作符
val length = nullableName?.length // 如果null返回null
// Elvis操作符提供默认值
val safeLength = nullableName?.length ?: 0
// 非空断言(慎用!)
val forceLength = nullableName!!.length // 可能抛NPE
血泪教训:在团队项目中,我们规定只有极特殊情况下才能用
!!,90%的NPE崩溃都源于滥用非空断言。
字符串模板远比简单的变量插入强大:
kotlin复制val price = 9.99
val quantity = 3
// 基本用法
println("单价:$price") // 输出:单价:9.99
// 表达式计算
println("总价:${price * quantity}") // 输出:总价:29.97
// 调用方法
val text = "Kotlin"
println("长度:${text.length}") // 输出:长度:6
处理多行文本时,用三个引号""":
kotlin复制val sql = """
SELECT * FROM users
WHERE name = ?
AND age > ?
""".trimIndent()
几个实用技巧:
trimIndent():去除每行前的公共缩进trimMargin():以指定字符对齐(默认|)kotlin复制val code = """
|fun main() {
| println("Hello")
|}
""".trimMargin()
Kotlin的智能转换能自动判断类型:
kotlin复制fun printLength(any: Any) {
if (any is String) {
// 这里any自动转为String类型
println(any.length)
}
}
Kotlin的标准库提供了几个超实用的作用域函数:
| 函数名 | 对象引用 | 返回值 | 适用场景 |
|---|---|---|---|
| let | it | 表达式值 | 非空检查后操作 |
| run | this | 表达式值 | 对象初始化 |
| with | this | 表达式值 | 对已知对象集中操作 |
| apply | this | 对象本身 | 对象配置 |
| also | it | 对象本身 | 额外操作如日志记录 |
实际案例:
kotlin复制// let处理可空
user?.let {
println("用户名:${it.name}")
sendEmail(it.email)
}
// apply配置对象
val dialog = AlertDialog.Builder(context).apply {
setTitle("提示")
setMessage("确定删除吗?")
setPositiveButton("确定") { _, _ -> }
setNegativeButton("取消") { _, _ -> }
}.create()
对于某些必须声明为可空但实际上不会为null的变量,可以用延迟初始化:
kotlin复制lateinit var heavyObject: HeavyClass
fun init() {
heavyObject = HeavyClass() // 实际使用前初始化
}
注意事项:
varUninitializedPropertyAccessException::heavyObject.isInitialized检查是否初始化在循环中拼接字符串时,StringBuilder性能更好:
kotlin复制// 反例(每次循环创建新String对象)
var result = ""
for (i in 1..1000) {
result += i.toString()
}
// 正例
val builder = StringBuilder()
for (i in 1..1000) {
builder.append(i)
}
val result = builder.toString()
Kotlin提供了专门的基本类型数组,性能更好:
kotlin复制// 普通对象数组(装箱开销)
val objArray: Array<Int> = arrayOf(1, 2, 3)
// 基本类型数组(无装箱)
val primitiveArray: IntArray = intArrayOf(1, 2, 3)
其他基本类型数组:
BooleanArrayCharArrayByteArrayShortArrayLongArrayFloatArrayDoubleArray避免空指针的几种方式对比:
| 方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 可空类型 | 编译时检查 | 需要处理null情况 | 所有可能为null的变量 |
| Elvis操作符 | 简洁 | 可能掩盖问题 | 提供默认值 |
| 安全调用 | 链式调用安全 | 可能传播null | 链式调用 |
| !!操作符 | 代码简洁 | 风险高 | 确定非空时 |
| lateinit | 避免可空声明 | 需确保初始化 | 生命周期明确的对象 |
com.example.project)MainActivity)userName)MAX_COUNT)isVisible)除了常规的单行(//)和多行(/* */)注释,Kotlin还支持文档注释:
kotlin复制/**
* 计算两个数的和
* @param a 第一个加数
* @param b 第二个加数
* @return 两数之和
*/
fun add(a: Int, b: Int): Int {
return a + b
}
几个注释技巧:
kotlin复制// TODO: 需要优化性能
fun heavyCalculation() { ... }
让我们用一个完整的商品模型来实践变量使用:
kotlin复制data class Product(
// 基本信息
val id: String,
var name: String,
var price: Double,
// 库存信息
var stock: Int = 0,
val maxPurchase: Int = 10,
// 状态标志
var isOnSale: Boolean = false,
var isDeleted: Boolean = false,
// 可选属性
var description: String? = null,
var tags: List<String> = emptyList()
) {
// 计算属性
val formattedPrice: String
get() = "¥%.2f".format(price)
// 业务方法
fun applyDiscount(rate: Double) {
require(rate in 0.0..1.0) { "折扣率必须在0-1之间" }
price *= (1 - rate)
isOnSale = true
}
}
这个案例展示了:
在商品详情页获取时,我们确实需要注意性能问题。比如网络请求应该放在协程中异步执行,数据库查询要确保有合适的索引。对于商品图片这类大资源,还需要实现懒加载和缓存机制。