
前言
变量 和常量 就像生活中的水杯 与玻璃雕塑 :水杯能随时装不同饮品(变量 ),玻璃雕塑一旦定型就永恒不变(常量 )。但 val 和 const val 藏着意想不到的玄机------有些"常量"竟会偷偷变化?
本章节将带你捅破变量与常量的窗户纸,避开新手90%的踩坑点。
操千曲 而后晓声,观千剑 而后识器。虐它千百遍 方能通晓其真意。
变量:var & val 🎭
var: 可变变量
-
核心特性 :变量可多次重新赋值 ,赋予数据 二次生命 的能力。
Kotlinvar score = 90 score = 95 // 分数重生 → ✅ -
声明及初始化 :类型一旦确定后不可修改!隐式声明时取决于第一次赋值的类型 。
Kotlinvar age: Int = 25 // 显式声明 var age = 18 // 隐式声明 age = "二十六" // ❌ 编译报错!类型已锁定为 Int💡 破解之道 :可声明为
Any类型,但需警惕类型安全风险。
val:只读变量(运行时常量)
-
核心特性(
不可变性):只能赋值一次,不可重新赋值 。 官方推荐的默认选择。尽可能地使用val可避免意外的状态改变,提高代码的可预测性 和线程安全性。 -
声明及初始化 :声明时必须初始化,或在类构造函数中初始化,或使用
lateinit或by lazy等委托进行延迟初始化。初始化后,其引用 (对于对象类型)或值 (对于基本类型)就不能再被改变。 -
颠覆认知(
对象内容可篡改) :只读≠内容不可变!Kotlinval androidVersions = arrayListOf("Oreo", "Pie") androidVersions.add("Q") // ✅ 集合内容被修改! androidVersions = newList // ❌ 引用不可变🧩 本质原因 :
val只保证引用地址不变(购物车还是那辆车),但车里的货物(对象内容)随便改! -
智能转换魔法 :类型推断升级
Kotlinval data: Any = "Kotlin" if (data is String) { print(data.length) // ✅ 编译器自动转为 String 类型 }⚡ 编译器秘密:在作用域内自动插入类型转换字节码。
-
延迟初始化 :
lateinit var的替代方案Kotlinval config: Configuration by lazy { loadConfigFromServer() // 首次访问时初始化 }🌟 优势:线程安全 + 按需加载 + 不可变引用
常量:const val(编译期常量) ⏳
const val:真正的化石
核心目的 是声明编译期常量。 想要彻底不可变 ?用 const val:
Kotlin
// 必须定义在顶层或 object 中
const val PI = 3.14159
const val COMPANY_NAME = "AndroidLab"
class MyClass {
companion object {
const val API_KEY = "ABCDEF123456" // 注意:实际API密钥应安全存储,这仅为示例
const val VERSION_CODE = 1
}
}
特性:
- 必须是顶层属性 或
object声明的成员。 - 必须在编译时就能确定值及其类型。
- 只能是基本类型 或
String。 - 不能有自定义的
getter。
编译期优化:
编译器会在编译期间直接用字面值替换所有使用该常量的地方 ,类似于宏替换(没有运行时的变量读取开销)。
val vs const val:生死对决
| 特性 | val |
const val (编译时常量) |
|---|---|---|
| 关键特性 | 运行时只读变量,引用不可变 | 编译期常量,值内联 |
| 声明位置 | 任何作用域(函数内、类属性等) | 仅顶层或 object(含伴生对象)内 |
| 允许的类型 | 所有类型 | 仅基本类型 (Int等) 和 String |
| 初始化要求 | 运行时初始化 | 编译时初始化 (字面量/其他const val) |
自定义 getter |
可以有 | 不能有 |
使用 lateinit |
可以 | 不能 |
by lazy |
可以 | 不能 |
| 内存开销 | 运行时存在变量 | 编译后替换为字面量,无运行时变量 |
| 主要用途 | 表示程序运行期间不变的引用 | 定义字面值的命名常量,用于注解参数等 |
🌰 举个栗子:
Kotlin
val currentTime: Long
get() = System.currentTimeMillis() // 每次调用值都变!
const val MAX_USERS = 1000 // 恒等于 1000
深入特性:藏在代码缝里的魔鬼 😈
类型推断的"障眼法"
Kotlin 能自动推断类型,但玩过头会翻车:
Kotlin
val number = 100 // 自动推断为 Int
number = 10.5 // ❌ 拒绝!Int 怎能变 Double?
急救方案:显式声明类型避免误伤:
Kotlin
val number: Number = 100 // 声明为父类
number = 10.5 // ✅ 此时合法!
幕后字段的"分身术"
自定义 getter/setter 时,偷偷生成幕后字段 (field):
Kotlin
var name: String = ""
set(value) {
field = value.trim() // field 是隐藏的存储变量
}
企业级实践:老司机的求生法则 🚀
-
优先
val:
除非明确需要修改,否则全用val------减少意外篡改(官方推荐)。 -
const val使用场景 :- 魔法数字(如超时时间、错误码)。
- 全局配置(API 密钥、常量字符串)。
Kotlinconst val API_TIMEOUT = 30_000 -
警惕可变
val!
如果val指向可变对象(如ArrayList),加防御性拷贝 :Kotlinval safeList = Collections.unmodifiableList(rawList) -
变量命名防混淆 :
- 变量 :驼峰命名(
userName)。 - 常量 :全大写
+下划线(USER_LIMIT)。
- 变量 :驼峰命名(
总结
Kotlin 用 var 和 val 编织了一张精妙的安全网🕸️------var 给灵活留了门,val 用引用不可变保护了基础安全,而 const val 才是真正的"代码化石"。 val 不等于内容不可变 ,遇到集合需警惕;const val 才是编译期铁律 ,零开销且绝对静止。掌握这三者,你的 Kotlin 代码将兼具弹性与稳定。✨
欢迎一键四连 (
关注+点赞+收藏+评论)