【Kotlin系统化精讲:叁】 | 变量与常量:自由与约束的代码博弈

前言

变量 和常量 就像生活中的水杯 与玻璃雕塑 ​:水杯能随时装不同饮品(变量 ),玻璃雕塑一旦定型就永恒不变(常量 )。但 val 和 const val 藏着意想不到的玄机------有些"常量"竟会偷偷变化?

本章节将带你捅破变量与常量的窗户纸,避开新手90%的踩坑点。

操千曲 而后晓声,观千剑 而后识器。虐它千百遍 方能通晓其真意。


变量:var & val 🎭

var: 可变变量

  • 核心特性 :变量可多次重新赋值 ,赋予数据 二次生命 的能力。

    Kotlin 复制代码
    var score = 90  
    score = 95  // 分数重生 → ✅   
  • 声明及初始化 :类型一旦确定后不可修改!隐式声明时取决于第一次赋值的类型 。

    Kotlin 复制代码
    var age: Int = 25  // 显式声明
    var age = 18 // 隐式声明
    age = "二十六"  // ❌ 编译报错!类型已锁定为 Int  

    💡 ​破解之道 ​:可声明为Any类型,但需警惕类型安全风险。


val:只读变量(运行时常量)

  • 核心特性(不可变性):只能赋值一次,不可重新赋值 。 官方推荐的默认选择。尽可能地使用 val 可避免意外的状态改变,提高代码的可预测性 和线程安全性。

  • 声明及初始化 :声明时必须初始化,或在类构造函数中初始化,或使用 lateinit 或 by lazy 等委托进行延迟初始化。初始化后,其引用 (对于对象类型)或值 (对于基本类型)就不能再被改变。

  • 颠覆认知(对象内容可篡改) :只读≠内容不可变!

    Kotlin 复制代码
    val androidVersions = arrayListOf("Oreo", "Pie")  
    androidVersions.add("Q")  // ✅ 集合内容被修改!  
    androidVersions = newList  // ❌ 引用不可变 

    🧩 ​本质原因 ​:val 只保证引用地址不变​(购物车还是那辆车),但车里的货物(对象内容)随便改!

  • 智能转换魔法 :类型推断升级

    Kotlin 复制代码
    val data: Any = "Kotlin"  
    if (data is String) {  
        print(data.length)  // ✅ 编译器自动转为 String 类型  
    }  

    ⚡ 编译器秘密:在作用域内自动插入类型转换字节码。

  • 延迟初始化 :lateinit var 的替代方案

    Kotlin 复制代码
    val 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 密钥、常量字符串)。
    Kotlin 复制代码
    const val API_TIMEOUT = 30_000  
  • 警惕可变 val!​
    如果 val 指向可变对象(如 ArrayList),加防御性拷贝 :

    Kotlin 复制代码
    val safeList = Collections.unmodifiableList(rawList)  
  • 变量命名防混淆 :

    • 变量 :驼峰命名(userName)。
    • 常量 :全大写+下划线(USER_LIMIT)。

总结

Kotlin 用 var 和 val 编织了一张精妙的安全网🕸️------var 给灵活留了门,val 用引用不可变保护了基础安全,而 const val 才是真正的"代码化石"。 val 不等于内容不可变 ,遇到集合需警惕;const val 才是编译期铁律 ,零开销且绝对静止。掌握这三者,你的 Kotlin 代码将兼具弹性与稳定。✨

欢迎一键四连 (关注 + 点赞 + 收藏 + 评论)

相关推荐
tongyiixiaohuang7 小时前
WMS已发货状态反写金蝶云星空:单策略实战教程
android
好的佩奇7 小时前
Dart 之类修饰符
android·flutter·dart
alexhilton9 小时前
用Konsist与Lint强制执行设计系统契约
android·kotlin·android jetpack
唐青枫11 小时前
Kotlin Delegation 委托详解
kotlin
北京自在科技13 小时前
谷歌安卓 Gemini 迎来界面改版,Find Hub 接入 Gemini 支持物品记忆查找
android·安卓
YB137515 小时前
Jetpack Compose 动画Modifier.animateItem()
android
2401_8858850416 小时前
巴林语音通知接口如何对接?系统告警语音API接入教程
android·前端·网络·python·语音识别
收纳过往16 小时前
数据库加索引的时机:慢查询日志说话
android·开发语言·数据库·python·低代码·rxjava
狂热开发者17 小时前
车载中控要给鸿蒙用户,能不能和 Android 共用一个二维码?
android·华为·harmonyos
.Bryce.18 小时前
T2-LLM评测不等于Agent评测
android·java·人工智能·后端·gpt·agent·1024程序员节