
前言
变量 和常量 就像生活中的水杯 与玻璃雕塑 :水杯能随时装不同饮品(变量 ),玻璃雕塑一旦定型就永恒不变(常量 )。但 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
代码将兼具弹性与稳定。✨
欢迎一键四连 (
关注
+点赞
+收藏
+评论
)