Kotlin快速入门
本文是Kotlin基础语法的快速入门指南,主要面向有一定编程基础的开发者。课程重点讲解了Kotlin的核心语法特性,包括:程序主函数的简化写法(fun main())、变量声明规则(var/val的区别)、字符串模板的使用($变量和${表达式})、函数定义与调用(包括默认参数和命名参数)、以及编码规范和注释语法。文章强调Kotlin相比Java的语法优势,如类型推断、字符串模板等特性,并提供了大量
一、章节介绍

本节课程是Android开发官方首选语言Kotlin的超快速入门内容,聚焦Kotlin基础语法核心,旨在帮助“0.1基础人群”(了解过任一编程语言)快速建立Kotlin编程思维,为后续Android开发或后端开发铺垫。课程覆盖程序入口、变量声明、字符串操作、函数定义与调用等核心模块,兼顾语法准确性与实战实用性,是程序员入门Kotlin的关键基础章节。
| 核心知识点 | 面试频率 |
|---|---|
| 程序主函数(入口点) | 低 |
| var/val变量声明与区别 | 高 |
| Kotlin字符串模板用法 | 高 |
| 函数定义、调用与参数规则 | 高 |
| 函数默认参数与命名参数 | 中 |
| 编码规范与程序可读性 | 低 |
| Kotlin注释语法 | 低 |
二、知识点详解
1. 程序主函数(入口点)
- 作用:Kotlin程序的“启动开关”,是JVM/编译器开始执行代码的唯一入口。
- 语法简化:Kotlin省略了Java中
main函数的String[] args参数(默认隐藏,需使用时可显式声明fun main(args: Array<String>)),基础写法如下:// 最简主函数写法,程序启动后执行花括号内代码 fun main() { println("程序启动成功") // 输出语句,类似Java的System.out.println } - 面试注意:需明确“主函数是程序唯一入口”,不可省略,且一个可执行Kotlin文件中仅需一个
main函数。
2. 编码规范与程序可读性
- 核心原则:遵循“小驼峰命名法”(变量/函数名首字母小写,如
userName、calculateScore),避免中文符号(如中文逗号、引号),缩进统一(推荐4个空格)。 - 重要性:编码规范不影响程序运行,但直接决定团队协作效率(如他人阅读代码的成本),企业开发中通常有强制规范(如阿里《Kotlin编码规范》)。
- 示例对比:
// 不规范:中文符号、命名混乱 val 用户名="张三"; // 中文变量名+中文分号,编译报错且可读性差 // 规范:小驼峰命名、英文符号 val userName = "张三" // 无冗余分号(Kotlin可省略行尾分号)
3. 变量声明:var与val(面试高频)
- 核心区别:
var(可变变量):声明后可多次赋值,适用于值需动态修改的场景(如计数器、用户输入数据)。val(不可变变量):声明时必须初始化,初始化后引用不可变(若为引用类型,对象内部属性可能可变),优先使用(提升线程安全与代码可维护性)。
- 类型推断:Kotlin会自动推导变量类型,无需显式声明(复杂场景需显式指定,如
val num: Long = 100)。 - 代码示例:
// var:可变变量(可修改值) var count = 0 count += 10 // 合法,输出10 // val:不可变变量(不可修改引用) val pi = 3.14159 // 自动推断为Double类型 // pi = 3.14 // 编译报错:val cannot be reassigned // val引用类型:对象内部属性可修改(若属性为var) data class User(var name: String) // 数据类,name为var val user = User("Alice") user.name = "Bob" // 合法,输出"Bob"(引用未变,内部属性修改)
4. 字符串模板(面试高频)
- 作用:替代传统字符串拼接(避免
"a=" + a + ", b=" + b的冗余写法),直接嵌入变量或表达式。 - 语法:
- 基础用法:
$变量名(嵌入单个变量)。 - 复杂表达式:
${表达式}(嵌入运算、函数调用等)。
- 基础用法:
- 代码示例:
val name = "Alice" val age = 25 val score = 88 // 基础用法:嵌入变量 val basicInfo = "姓名:$name,年龄:$age" println(basicInfo) // 输出:姓名:Alice,年龄:25 // 复杂表达式:嵌入运算+函数调用 fun getGrade(score: Int): String = if (score >= 90) "A" else "B" val detailInfo = "成绩:$score,等级:${getGrade(score)},年龄+5:${age + 5}" println(detailInfo) // 输出:成绩:88,等级:B,年龄+5:30
5. 注释语法
- 与Java完全一致,无特殊差异:
- 单行注释:
// 注释内容(常用于单行说明)。 - 多行注释:
/* 注释内容 */(常用于多行说明,不可嵌套)。 - 文档注释:
/** 注释内容 */(用于函数/类的文档生成,支持Markdown)。
- 单行注释:
- 代码示例:
// 单行注释:这是一个计算两数之和的函数 /** * 文档注释:计算两个整数的和 * @param a 第一个整数 * @param b 第二个整数 * @return 两数之和 */ fun add(a: Int, b: Int): Int { /* 多行注释: 1. 接收两个参数a和b 2. 返回a + b的结果 */ return a + b }
6. 函数定义与调用(面试高频)
- 基础结构:
fun 函数名(参数列表): 返回类型 { 函数体 },无返回值时可省略返回类型(默认Unit,类似Java的void)。 - 核心特性:主函数与自定义函数的位置无限制(可先写自定义函数,再写
main函数)。 - 代码示例:
// 自定义函数:无返回值(省略Unit) fun printWelcomeMsg() { println("欢迎学习Kotlin!") } // 自定义函数:有返回值(显式声明Int) fun multiply(a: Int, b: Int): Int { return a * b } // 主函数:调用自定义函数 fun main() { printWelcomeMsg() // 调用无返回值函数 val result = multiply(3, 4) // 调用有返回值函数 println("3*4=$result") // 输出:3*4=12 }
7. 函数参数规则(默认参数+命名参数)
- 多参数声明:参数之间用“逗号+空格”分隔,形参需指定类型(Kotlin函数参数不可省略类型)。
- 默认参数:参数声明时指定默认值(
参数名: 类型 = 默认值),调用时可省略该参数。 - 命名参数:调用函数时指定参数名,可打乱参数顺序(解决多同类型参数的顺序混淆问题)。
- 代码示例:
// 带默认参数的函数:discount默认值为1.0(无折扣) fun calculateTotal(price: Double, quantity: Int, discount: Double = 1.0): Double { return price * quantity * discount } fun main() { // 1. 省略默认参数:使用discount=1.0 val total1 = calculateTotal(100.0, 2) println("无折扣总价:$total1") // 输出:200.0 // 2. 显式传入默认参数:修改discount=0.8 val total2 = calculateTotal(100.0, 2, 0.8) println("8折总价:$total2") // 输出:160.0 // 3. 命名参数:打乱参数顺序(quantity和price位置互换) val total3 = calculateTotal(quantity = 3, price = 50.0, discount = 0.9) println("9折总价:$total3") // 输出:135.0(50.0*3*0.9) }
三、章节总结
本节围绕Kotlin基础语法核心,覆盖“程序入口-变量-字符串-函数”四大模块,核心要点可概括为:
- 主函数是程序唯一入口,语法简化为
fun main() { ... }; - 变量优先用
val(不可变),仅动态值用var(可变),依赖类型推断减少冗余代码; - 字符串模板用
$变量或${表达式},替代传统拼接; - 函数支持默认参数(省略重复传值)和命名参数(避免顺序错误),参数必须指定类型;
- 编码规范(小驼峰、英文符号)和注释语法与Java一致,需严格遵循以提升协作效率。
四、知识点补充
1. 补充知识点(5个核心扩展)
| 补充知识点 | 说明与代码示例 |
|---|---|
| 可空类型(面试高) | 变量后加?表示可空(避免空指针异常),调用时需安全处理(?.或?:):val age: Int? = nullval safeAge = age ?: 0 // 为空时取默认值0 |
| 函数表达式(面试中) | 无块体函数可简化为fun 函数名(参数): 返回类型 = 表达式:fun add(a: Int, b: Int) = a + b // 等价于带return的写法 |
| 常量定义(const val) | 编译期常量(仅支持基础类型/String),需声明在顶层或object内:const val BASE_URL = "https://api.example.com" |
| 多行字符串(实用) | 用"""包裹,保留格式(无需转义换行/引号):val poem = """床前明月光,疑是地上霜""" |
| 类型强制转换(安全) | 用as?(失败返回null)替代as(失败抛异常):val obj: Any = "test" val str = obj as? String // 安全转换为String |
2. 最佳实践:Kotlin变量声明的“不可变优先”原则
在Kotlin开发中,“优先使用val,仅必要时用var”是企业级开发的核心最佳实践,其价值体现在三个维度:
- 线程安全:多线程环境下,不可变变量(val)无需加锁即可安全访问,避免因“多个线程修改同一变量”导致的竞态条件(如计数器错乱)。例如Android开发中,
val text = "首页标题"作为TextView的文本,多线程下不会因修改导致UI显示异常。 - 代码可维护性:val变量初始化后引用不可变,后续代码无需追踪其值的变化(如var变量可能在多个函数中被修改,排查bug时需逐行查找赋值点)。例如定义
val userList: List<User> = api.getUserList(),后续仅能读取列表元素,无法修改列表结构,减少“无意识修改”导致的bug。 - 符合函数式编程思想:Kotlin支持函数式编程(如lambda、流操作),不可变是函数式的核心特性之一。例如使用
userList.filter { it.age > 18 }时,val保证原列表不被修改,过滤结果生成新列表,符合“无副作用”的函数式原则。
反例:滥用var导致的问题
// 不推荐:用var声明无需修改的变量
var appName = "MyApp"
// 后续代码可能误修改(如他人接手时添加appName = "YourApp"),导致全局配置错乱
// 推荐:用val声明不可变变量
val appName = "MyApp"
// 后续无法修改,避免误操作
3. 编程思想指导:Kotlin的“简洁性与安全性平衡”
Kotlin的设计哲学是“在简化语法的同时,不牺牲安全性”,这一思想贯穿从基础语法到高级特性的所有环节,是程序员编写高质量Kotlin代码的核心指导:
-
简洁性:减少样板代码,提升开发效率
- 类型推断:无需显式声明
String name = "Alice",简化为val name = "Alice",减少冗余的类型书写,同时编译器自动校验类型合法性。 - 函数表达式:用
fun getArea(r: Double) = Math.PI * r * r替代“函数体+return”的冗余写法,代码更短且意图明确。 - 字符串模板:用
"用户$name的年龄是$age"替代"用户" + name + "的年龄是" + age,避免拼接时漏加+号或括号导致的语法错误。
- 类型推断:无需显式声明
-
安全性:通过语法约束,规避常见bug
- 空安全:强制区分可空类型(
Int?)和非空类型(Int),避免Java中“空指针异常”这一最常见的运行时错误。例如val age: Int? = null,调用age.plus(1)会编译报错,需用age?.plus(1) ?: 0安全处理。 - 不可变优先:val的设计引导程序员减少可变状态,从语法层面限制“变量随意修改”,降低并发bug和逻辑错误的概率。
- 严格的类型检查:编译期校验类型兼容性,例如
val num: Int = "123"会直接编译报错,避免Java中“运行时类型转换异常”。
- 空安全:强制区分可空类型(
实践示例:平衡简洁与安全
// Java代码:冗余且不安全(可能抛NullPointerException)
String userName = getUser().getName(); // getUser()可能返回null,getName()可能返回null
int userNameLength = userName.length(); // 若userName为null,运行时崩溃
// Kotlin代码:简洁且安全
val userName = getUser()?.name ?: "未知用户" // 安全调用+Elvis运算符,避免空指针
val userNameLength = userName.length // 无需显式类型声明,编译器自动推断String类型
程序员在编写Kotlin代码时,应主动利用这些特性:不写冗余的类型声明(如val num = 10而非val num: Int = 10),同时严格遵循空安全规则(避免用!!强制非空,除非100%确定变量非空),让代码既简洁易读,又健壮可靠。
五、程序员面试题
1. 简单题:Kotlin中的var和val有什么区别?分别适用于什么场景?
答案:
- 区别:
- 可变性:
var是可变变量,声明后可多次赋值修改值;val是不可变变量,声明时必须初始化,初始化后引用不可变(引用类型的内部属性可能可变)。 - 编译约束:
val变量赋值后若再次赋值,会直接编译报错;var无此约束。
- 可变性:
- 适用场景:
val:优先使用,适用于值无需修改的场景(如固定配置、初始化后不变的对象引用,如val baseUrl = "https://api.example.com"),提升线程安全和代码可维护性。var:仅用于值需动态修改的场景(如计数器、用户输入后的动态数据,如var count = 0; count++)。
2. 中等题:请解释Kotlin字符串模板的用法,并编写代码示例展示基础用法和复杂表达式用法。
答案:
Kotlin字符串模板用于在字符串中嵌入变量或表达式,无需手动拼接,语法简洁且避免拼接错误,核心用法分为两类:
- 基础用法:用
$变量名直接嵌入单个变量(支持任意数据类型)。 - 复杂表达式用法:用
${表达式}包裹运算、函数调用、逻辑判断等复杂逻辑。
代码示例:
fun main() {
// 1. 基础用法:嵌入变量
val product = "手机"
val price = 3999.0
val stock = 100
val basicDesc = "商品:$product,单价:$price 元,库存:$stock 台"
println(basicDesc)
// 输出:商品:手机,单价:3999.0 元,库存:100 台
// 2. 复杂表达式用法:嵌入运算+函数调用
// 定义辅助函数:计算折扣价
fun getDiscountPrice(originalPrice: Double, discount: Double = 0.9): Double {
return String.format("%.2f", originalPrice * discount).toDouble() // 保留2位小数
}
// 嵌入表达式:折扣价、库存是否充足、总价计算
val discount = 0.85
val detailDesc = """
商品详情:
- 折扣价:${getDiscountPrice(price, discount)} 元(原价$price 元,折扣$discount)
- 库存状态:${if (stock > 50) "充足" else "紧张"}
- 购买2台总价:${getDiscountPrice(price, discount) * 2} 元
""".trimIndent() // trimIndent()去除多行字符串的默认缩进
println(detailDesc)
// 输出:
// 商品详情:
// - 折扣价:3399.15 元(原价3999.0 元,折扣0.85)
// - 库存状态:充足
// - 购买2台总价:6798.3 元
}
3. 中等题:Kotlin函数的默认参数和命名参数有什么作用?请编写代码示例说明如何使用,并解释命名参数的一个核心优势。
答案:
- 作用:
- 默认参数:允许函数参数声明时指定默认值,调用时可省略该参数,减少“重载函数”的编写(如无需为“带折扣”和“无折扣”分别写两个函数)。
- 命名参数:允许调用函数时指定参数名,无需按形参顺序传递,解决“多同类型参数顺序混淆”的问题。
代码示例:
// 定义带默认参数的函数:计算订单总价(discount默认1.0,isVip默认false)
fun calculateOrderTotal(
goodsPrice: Double, // 商品原价
quantity: Int, // 购买数量
discount: Double = 1.0, // 折扣(默认无折扣)
isVip: Boolean = false // 是否VIP(默认非VIP)
): Double {
// VIP额外打9.5折
val finalDiscount = if (isVip) discount * 0.95 else discount
return goodsPrice * quantity * finalDiscount
}
fun main() {
// 1. 省略默认参数:仅传必填参数(goodsPrice、quantity)
val total1 = calculateOrderTotal(200.0, 3)
println("普通用户无折扣总价:$total1") // 输出:600.0(200*3*1.0)
// 2. 显式传入部分默认参数:修改discount,保留isVip默认值
val total2 = calculateOrderTotal(200.0, 3, 0.8)
println("普通用户8折总价:$total2") // 输出:480.0(200*3*0.8)
// 3. 命名参数:打乱参数顺序,明确传递意图
val total3 = calculateOrderTotal(
quantity = 3,
goodsPrice = 200.0,
isVip = true,
discount = 0.8
)
println("VIP用户8折总价:$total3") // 输出:456.0(200*3*0.8*0.95)
}
// 命名参数核心优势:避免多同类型参数的顺序错误
// 反例(无命名参数):若函数参数为calculateOrderTotal(200.0, 0.8, 3),会将0.8误传为quantity(Int类型),编译报错;
// 正例(命名参数):calculateOrderTotal(goodsPrice=200.0, discount=0.8, quantity=3),参数顺序无关,意图明确,无错误风险。
4. 高难度题:Kotlin函数参数的传递机制是值传递还是引用传递?请结合代码示例分析,并说明对可变对象和不可变对象的影响。
答案:
Kotlin函数参数传递机制本质是值传递,即传递的是参数的“值副本”:
- 对于基本数据类型(如Int、Double、Boolean):传递的是“具体数值的副本”,函数内修改副本不影响原变量。
- 对于引用数据类型(如类对象、数组、集合):传递的是“对象引用的副本”(而非对象本身),函数内通过副本可操作原对象,但无法修改原引用的指向。
代码示例分析
// 1. 基本数据类型(Int):传递数值副本
fun modifyBasicType(num: Int) {
var localNum = num // 局部副本
localNum += 5 // 修改副本,不影响原变量
println("函数内修改后:$localNum") // 输出:15
}
val originalNum = 10
modifyBasicType(originalNum)
println("原变量值:$originalNum") // 输出:10(无变化)
// 2. 引用数据类型(可变对象:自定义类User,name为var)
class User(var name: String, val id: Int) // name可变,id不可变
fun modifyMutableObject(user: User) {
// 情况1:修改对象内部可变属性(通过引用副本操作原对象)
user.name = "Bob" // 引用副本指向原对象,修改内部属性会影响原对象
println("函数内修改后name:${user.name}") // 输出:Bob
// 情况2:修改引用副本本身(指向新对象,不影响原引用)
val newUser = User("Charlie", 2)
user = newUser // 仅修改局部副本的指向,原引用仍指向原对象
println("函数内新对象name:${user.name}") // 输出:Charlie
}
val originalUser = User("Alice", 1)
modifyMutableObject(originalUser)
println("原对象name:${originalUser.name}") // 输出:Bob(内部属性被修改)
println("原对象id:${originalUser.id}") // 输出:1(id为val,不可修改)
// 3. 引用数据类型(不可变对象:String,底层为不可变字符序列)
fun modifyImmutableObject(str: String) {
val newStr = str + "_modified" // String不可变,拼接生成新对象
println("函数内新字符串:$newStr") // 输出:test_modified
}
val originalStr = "test"
modifyImmutableObject(originalStr)
println("原字符串:$originalStr") // 输出:test(无变化)
对可变对象与不可变对象的影响总结
| 对象类型 | 影响 |
|---|---|
| 不可变对象 | 如String、val声明的data class(属性全为val),因对象内部属性不可修改,即使传递引用副本,也无法改变原对象,完全符合值传递“无副作用”的特性。 |
| 可变对象 | 如var声明的类对象、MutableList,通过引用副本可修改原对象的内部属性(如User的name、MutableList的add操作),但无法修改原引用的指向(如不能让原变量指向新对象)。需注意:这种“内部修改”可能导致无意识的副作用(如函数外的对象被意外修改),建议传递可变对象时明确函数意图(如函数名加modifyXXX),或优先使用不可变对象。 |
5. 高难度题:在Kotlin中,若一个函数同时存在默认参数和函数重载,调用时如何确定优先级?请编写代码示例说明可能的歧义场景及解决方案。
答案:
Kotlin函数调用的优先级核心规则:精确匹配(参数数量、类型完全一致)的重载函数优先级高于带默认参数的函数;若不存在精确匹配的重载,且带默认参数的函数可通过省略默认参数满足调用参数数量,则调用带默认参数的函数;若存在多个函数均可匹配(歧义),则编译报错,需显式消除歧义。
1. 无歧义场景:精确匹配重载优先
// 场景1:带默认参数的函数与重载函数
fun sendMsg(content: String, receiver: String = "all") { // 2参数,1默认
println("默认参数版:向$receiver发送消息:$content")
}
fun sendMsg(content: String) { // 重载函数(1参数,精确匹配)
println("重载版:发送全局消息:$content")
}
fun main() {
// 调用1:参数数量1,精确匹配重载函数sendMsg(content: String)
sendMsg("Hello World")
// 输出:重载版:发送全局消息:Hello World
// 调用2:参数数量2,精确匹配带默认参数的函数(无重载)
sendMsg("Hi", "Alice")
// 输出:默认参数版:向Alice发送消息:Hi
}
2. 歧义场景:多个函数均可匹配,编译报错
// 场景2:歧义场景(新增重载函数导致匹配冲突)
fun calculate(a: Int, b: Int = 5): Int { // 带默认参数(2参数,1默认)
return a + b // 加法
}
fun calculate(a: Int): Int { // 重载函数(1参数,乘法)
return a * 2
}
fun calculate(a: Int, b: Int): Int { // 新增重载函数(2参数,减法)
return a - b
}
fun main() {
// 调用1:参数数量1,精确匹配重载函数calculate(a: Int),无歧义
println(calculate(3)) // 输出:6(3*2)
// 调用2:参数数量2,精确匹配重载函数calculate(a: Int, b: Int),无歧义
println(calculate(5, 2)) // 输出:3(5-2)
// 调用3:歧义场景(新增带默认参数的函数,参数类型兼容)
fun calculate(a: Int, b: Any = 0): Int { // 新增:b为Any类型(Int的父类)
return a + (b as Int) // 强制转换为Int
}
// 调用calculate(5, 3):此时以下两个函数均可匹配:
// 1. calculate(a: Int, b: Int)(精确匹配Int类型)
// 2. calculate(a: Int, b: Any = 0)(Any是Int的父类,兼容匹配)
// calculate(5, 3) // 编译报错:Overload resolution ambiguity
}
3. 歧义解决方案
针对上述编译报错场景,可通过以下方式消除歧义:
-
显式指定参数类型:通过类型转换明确匹配的函数,例如:
// 显式将b转换为Int,匹配calculate(a: Int, b: Int) calculate(5, 3 as Int) // 输出:3(5-2,此处3为示例,实际参数为3时输出5-3=2) -
使用命名参数:通过参数名明确指定目标函数(适用于参数名不同或需区分默认参数的场景),例如:
// 若函数参数名不同,可通过命名参数区分;此处参数名均为b,需结合类型 calculate(a = 5, b = 3 as Int) // 同上,明确匹配Int类型的重载函数 -
调整函数设计(推荐):删除冗余的重载或默认参数,从根源避免歧义。例如删除
calculate(a: Int, b: Any = 0),或修改其参数名(如b: Any改为c: Any),确保每个调用仅能匹配一个函数。

Kotlin进阶核心知识点
1. 作用域函数(let/run/with/apply/also)
核心说明
Kotlin提供的5个内置作用域函数,用于简化对象非空判断、对象初始化配置、代码作用域隔离,核心作用是减少临时变量、让代码更简洁,是Android开发中最常用的进阶语法(如View初始化、对象判空)。
5个函数的核心区别:是否持有对象引用(this/it)、是否返回对象本身/执行结果。
核心用法+代码示例
data class User(var name: String, var age: Int)
fun main() {
val user: User? = User("Alice", 25) // 可空User对象
// 1. let:引用为it,返回执行结果,适用于非空执行+结果处理
val nameLength = user?.let {
it.name = "Bob" // it指代非空的user
it.name.length // 返回结果:3
} ?: 0 // 非空判空兜底
println(nameLength) // 输出:3
// 2. apply:引用为this,返回对象本身,适用于对象初始化/配置(Android View初始化高频)
val newUser = User("Tom", 30).apply {
age = 35 // this可省略,指代newUser
name = "Tommy"
}
println(newUser) // 输出:User(name=Tommy, age=35)
// 3. also:引用为it,返回对象本身,适用于对象操作+保留原对象(如日志打印)
val user2 = User("Lily", 20).also {
println("初始化用户:$it") // 打印日志,不影响原对象
}.apply {
age += 5 // 继续配置
}
println(user2) // 输出:User(name=Lily, age=25)
// 4. with:入参为非空对象,引用为this,返回执行结果,适用于非空对象的多操作
val userInfo = with(newUser) {
"姓名:$name,年龄:$age" // 返回字符串结果
}
println(userInfo) // 输出:姓名:Tommy,年龄:35
// 5. run:结合let+with,可空调用+this引用+返回结果,适用于复杂逻辑
val runResult = user?.run {
age = 40
name + "_" + age // 返回结果:Bob_40
}
println(runResult) // 输出:Bob_40
}
实战场景
Android中TextView初始化:tv_title?.apply { text = "标题"; textSize = 18f; setTextColor(Color.BLACK) },无需重复写变量名。
2. 空安全进阶(lateinit/by lazy/::isInitialized)
核心说明
针对Kotlin非空类型的延迟初始化需求(如Android中View、ViewModel无法在声明时初始化,但又不想用可空类型?),提供lateinit和by lazy两种解决方案,补充::变量.isInitialized判断延迟初始化状态,彻底规避空指针异常。
核心特性
| 关键字 | 适用场景 | 可变性 | 初始化时机 | 线程安全 |
|---|---|---|---|---|
| lateinit | 可变非空对象(如View、普通类) | var | 手动调用赋值时 | 否 |
| by lazy | 不可变非空对象(如配置、单例) | val | 首次调用时 | 是(默认) |
代码示例
// 场景1:lateinit + 初始化判断(Android View初始化高频)
class MainActivity {
private lateinit var tvTitle: TextView // 可变非空,延迟初始化
fun initView(view: TextView) {
tvTitle = view // 手动初始化
}
fun updateTitle() {
// 判断是否已初始化,避免未初始化调用报错
if (::tvTitle.isInitialized) {
tvTitle.text = "已初始化"
}
}
}
// 场景2:by lazy 延迟初始化(不可变,首次调用时初始化)
val appConfig by lazy {
// 首次调用时执行,后续直接返回结果,默认线程安全
mapOf("base_url" to "https://api.example.com", "timeout" to 5000)
}
fun main() {
println("未调用appConfig,未初始化")
println(appConfig["base_url"]) // 首次调用,执行初始化逻辑
println(appConfig["timeout"]) // 直接返回缓存结果
}
注意点
lateinit仅支持引用类型(如类、View),不支持基础类型(Int、Double);by lazy仅支持val,且初始化逻辑不可抛出空值。
3. 数据类(data class)
核心说明
用于仅承载数据的实体类(如接口返回的实体、数据库模型),Kotlin自动为其生成equals()/hashCode()/toString()/copy()/componentN()(解构赋值)方法,无需手动编写,解决Java中实体类的样板代码问题,是开发中最常用的特殊类。
核心特性
- 主构造函数必须包含至少一个参数;
- 参数建议用
val(不可变),若需修改用var; - 自动生成的
copy()方法支持对象浅拷贝,可快速修改部分属性。
代码示例
// 定义数据类(用户实体)
data class User(val id: Int, val name: String, val age: Int)
fun main() {
val user1 = User(1, "Alice", 25)
val user2 = User(1, "Alice", 25)
val user3 = User(2, "Bob", 30)
// 自动生成toString():替代Java的手动重写
println(user1) // 输出:User(id=1, name=Alice, age=25)
// 自动生成equals()/hashCode():值相等则对象相等
println(user1 == user2) // 输出:true
println(user1 == user3) // 输出:false
// 自动生成copy():浅拷贝,支持修改部分属性
val user4 = user1.copy(age = 26, name = "Alice2")
println(user4) // 输出:User(id=1, name=Alice2, age=26)
// 解构赋值(componentN()):按主构造函数顺序提取属性
val (id, name, age) = user1
println("解构:id=$id, name=$name, age=$age") // 输出:解构:id=1, name=Alice, age=25
}
实战场景
Android中Retrofit接口返回的实体类、Room数据库的实体类,全部使用data class简化开发。
4. 密封类(sealed class)
核心说明
受限的继承体系,子类只能在密封类内部或同一文件中定义,用于表示有限的、固定的状态集合(如网络请求状态:成功/失败/加载中、UI状态:显示/隐藏/加载),是Kotlin中替代枚举类的更灵活方案,when表达式匹配密封类子类时,无需写else分支(编译器会检查所有子类)。
核心特性
- 密封类本身是抽象类,无法实例化;
- 子类可以是普通类、数据类、对象;
- when表达式匹配时,覆盖所有子类则无需else,编译期校验,避免遗漏状态。
代码示例
// 定义密封类:网络请求状态(同一文件中定义子类)
sealed class NetworkState {
// 子类:加载中
object Loading : NetworkState()
// 子类:成功(携带数据)
data class Success<T>(val data: T) : NetworkState()
// 子类:失败(携带错误信息)
data class Error(val msg: String) : NetworkState()
}
// 处理网络请求状态
fun <T> handleNetworkState(state: NetworkState) {
when (state) {
is NetworkState.Loading -> println("加载中...")
is NetworkState.Success<*> -> println("请求成功:${state.data}")
is NetworkState.Error -> println("请求失败:${state.msg}")
// 无需else,编译器已检查所有子类
}
}
fun main() {
handleNetworkState(NetworkState.Loading) // 输出:加载中...
handleNetworkState(NetworkState.Success("用户数据:Alice")) // 输出:请求成功:用户数据:Alice
handleNetworkState(NetworkState.Error("网络超时")) // 输出:请求失败:网络超时
}
实战场景
Android中ViewModel的状态管理、网络请求的结果处理、UI的状态切换,用密封类替代枚举,支持携带数据(枚举无法携带动态数据)。
5. 扩展函数/扩展属性
核心说明
在不修改原类源码的前提下,为现有类添加新的函数/属性,是Kotlin的静态解析特性(无继承,无重写),用于简化工具类代码(如Java中的StringUtils、ViewUtils),Android开发中高频用于封装View、String、Collection等类的通用方法。
核心特性
- 扩展函数的this指代被扩展的类的实例;
- 扩展属性无法直接定义字段,需通过
get()/set()实现(无幕后字段); - 若原类有同名函数,原类函数优先级更高。
代码示例
// 示例1:为String类添加扩展函数:判空+去除空格
fun String?.safeTrim(): String {
// this指代调用的String对象,可空类型需判空
return this ?: ""
}
// 示例2:为Int类添加扩展函数:判断是否为偶数
fun Int.isEven(): Boolean {
return this % 2 == 0
}
// 示例3:为TextView添加扩展属性:设置是否显示(Android实战)
var TextView.isVisible: Boolean
get() = visibility == View.VISIBLE
set(value) {
visibility = if (value) View.VISIBLE else View.GONE
}
fun main() {
val str: String? = null
println(str.safeTrim()) // 输出:""
println(" Hello ".safeTrim()) // 输出:" Hello "(可结合trim()优化为this?.trim() ?: "")
val num = 6
println(num.isEven()) // 输出:true
}
实战场景
封装通用工具方法:String?.isPhone()(手机号校验)、List?.safeGet(index: Int)(集合安全取值)、View.click(listener: OnClickListener)(简化点击事件),替代Java的工具类。
6. 高阶函数与Lambda表达式
核心说明
高阶函数:将函数作为参数或返回值的函数,是Kotlin函数式编程的核心;Lambda表达式:简化函数类型参数的写法,避免匿名内部类的样板代码,二者结合是Android开发中简化回调、事件处理的关键(如点击事件、集合遍历)。
核心语法
- 函数类型声明:
(参数类型1, 参数类型2) -> 返回值类型(无返回值则为() -> Unit); - Lambda表达式:
{ 参数1: 类型, 参数2: 类型 -> 执行逻辑 }(参数类型可省略,编译器自动推断)。
代码示例
// 示例1:定义高阶函数:传入两个Int和一个运算函数,返回运算结果
fun calculate(a: Int, b: Int, operation: (Int, Int) -> Int): Int {
return operation(a, b)
}
// 示例2:定义高阶函数:无参数,无返回值(回调函数)
fun doSomething(callback: () -> Unit) {
println("执行前置逻辑")
callback() // 执行回调
println("执行后置逻辑")
}
fun main() {
// 调用calculate,使用Lambda作为运算函数
val sum = calculate(3, 5) { a, b -> a + b } // 加法
val multiply = calculate(3, 5) { a, b -> a * b } // 乘法
println("和:$sum,积:$multiply") // 输出:和:8,积:15
// 调用doSomething,使用Lambda作为回调
doSomething {
println("执行回调逻辑")
}
// 输出:
// 执行前置逻辑
// 执行回调逻辑
// 执行后置逻辑
// 集合遍历(Kotlin内置高阶函数,底层用Lambda)
val list = listOf(1, 2, 3)
list.forEach { println(it) } // 简化遍历,替代for循环
}
实战场景
Android中View的点击事件:btn_click.setOnClickListener { Toast.makeText(this, "点击了", Toast.LENGTH_SHORT).show() },替代Java的匿名内部类;Retrofit的回调、RxJava的事件处理均使用Lambda简化。
7. 内联函数(inline)
核心说明
用于优化高阶函数的性能问题,因为Kotlin中Lambda表达式会被编译为匿名内部类,频繁调用会产生大量临时对象,导致内存开销;inline关键字会让编译器在调用处直接内联展开Lambda的代码,而非创建匿名内部类,彻底消除性能损耗。
核心特性
- 仅用于参数包含函数类型的高阶函数;
- 用
noinline修饰不需要内联的函数类型参数; - 用
crossinline修饰需要在其他作用域执行的函数类型参数(如异步回调)。
代码示例
// 定义内联高阶函数:优化Lambda的性能
inline fun inlineDoSomething(callback: () -> Unit) {
println("内联函数前置逻辑")
callback()
println("内联函数后置逻辑")
}
// 普通高阶函数(对比)
fun normalDoSomething(callback: () -> Unit) {
println("普通函数前置逻辑")
callback()
println("普通函数后置逻辑")
}
fun main() {
// 调用内联函数:编译器会直接将callback的代码展开到此处,无匿名内部类
inlineDoSomething {
println("内联Lambda逻辑")
}
// 调用普通函数:编译器会创建匿名内部类,产生临时对象
normalDoSomething {
println("普通Lambda逻辑")
}
}
实战场景
所有自定义的高阶函数(如工具类中的回调、事件处理)都建议添加inline关键字;Kotlin内置的forEach、let、apply等作用域函数均为内联函数。
8. 智能类型转换与安全类型转换
核心说明
针对Kotlin的类型检查与转换,提供智能类型转换(is/!is) 和安全类型转换(as?),替代Java的instanceof+强制类型转换,彻底规避ClassCastException(类型转换异常),是类型处理的核心进阶语法。
核心特性
- 智能类型转换:用
is判断类型后,编译器自动将对象转换为该类型,无需手动转换; - 安全类型转换:用
as?替代as,转换失败时返回null而非抛出异常,结合?:兜底更安全。
代码示例
// 父类
open class Animal(val name: String)
// 子类1
class Dog(name: String, val bark: String) : Animal(name)
// 子类2
class Cat(name: String, val meow: String) : Animal(name)
fun handleAnimal(animal: Animal) {
// 智能类型转换:is判断后,自动转为Dog/Cat
if (animal is Dog) {
println("狗:${animal.name},叫声:${animal.bark}") // 无需手动转换
} else if (animal is Cat) {
println("猫:${animal.name},叫声:${animal.meow}")
}
}
// 安全类型转换
fun safeCast(obj: Any): String? {
// as?转换为String,失败返回null
return obj as? String
}
fun main() {
handleAnimal(Dog("旺财", "汪汪汪")) // 输出:狗:旺财,叫声:汪汪汪
handleAnimal(Cat("咪咪", "喵喵喵")) // 输出:猫:咪咪,叫声:喵喵喵
println(safeCast("Hello")) // 输出:Hello
println(safeCast(123)) // 输出:null(转换失败)
// 结合?:兜底
val str = safeCast(456) ?: "默认值"
println(str) // 输出:默认值
}
实战场景
Android中View的类型转换(如findViewById获取的View转换为TextView):val tv = view as? TextView ?: return,安全避免类型转换异常。
9. 委托(类委托/属性委托)
核心说明
基于委托模式的语法糖,Kotlin通过by关键字实现类委托和属性委托,将类的方法/属性的实现委托给其他对象,实现代码复用、解耦,是Kotlin中替代继承、简化重复逻辑的核心特性(如单例、SharedPreferences封装)。
核心分类
(1)类委托:实现接口时,将接口方法委托给其他对象,无需手动实现所有方法
(2)属性委托:将属性的get()/set()委托给其他对象,简化属性的读写逻辑
代码示例
// 示例1:类委托(实现List接口,委托给ArrayList)
class MyList<T> : List<T> by ArrayList<T>() {
// 仅重写需要自定义的方法,其他方法由ArrayList实现
override fun isEmpty(): Boolean {
println("自定义isEmpty方法")
return super.isEmpty()
}
}
// 示例2:属性委托(自定义委托类,实现属性的读写)
class PreferenceDelegate<T>(private val key: String, private val defaultValue: T) {
// 模拟SharedPreferences
private val sp = mutableMapOf<String, Any?>()
// 委托get()
operator fun getValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>): T {
return sp[key] as? T ?: defaultValue
}
// 委托set()
operator fun setValue(thisRef: Any?, property: kotlin.reflect.KProperty<*>, value: T) {
sp[key] = value
}
}
// 封装SharedPreferences委托(Android实战)
class SPManager {
// 委托给PreferenceDelegate,实现自动读写
var userName by PreferenceDelegate("user_name", "")
var userAge by PreferenceDelegate("user_age", 0)
}
fun main() {
// 类委托测试
val myList = MyList<Int>()
myList.add(1) // 调用ArrayList的add方法
println(myList.isEmpty()) // 调用自定义的isEmpty方法,输出:自定义isEmpty方法 + false
// 属性委托测试
val sp = SPManager()
sp.userName = "Alice"
sp.userAge = 25
println("用户名:${sp.userName},年龄:${sp.userAge}") // 输出:用户名:Alice,年龄:25
}
实战场景
- 类委托:Android中实现自定义集合、自定义View的接口方法,复用系统类的实现;
- 属性委托:封装Android的
SharedPreferences、ViewModel的状态属性,简化读写逻辑;Kotlin内置的by lazy也是属性委托的一种。
10. 枚举类(enum class)
核心说明
用于表示有限的、固定的常量集合(如性别:男/女、季节:春/夏/秋/冬、按钮状态:正常/按下/禁用),相比Java的枚举,Kotlin的枚举类支持实现接口、定义属性和方法,更灵活,是替代常量定义(const val)的更规范方案。
核心特性
- 枚举常量是枚举类的实例,直接通过
枚举类.常量调用; - 可定义属性和方法,每个常量可单独传参;
- 支持
when表达式匹配,无需else分支(编译器检查所有常量)。
代码示例
// 定义枚举类:性别(带属性,实现接口)
interface GenderDesc {
fun getDesc(): String
}
enum class Gender(val code: Int) : GenderDesc {
MALE(1) {
override fun getDesc(): String = "男性"
},
FEMALE(2) {
override fun getDesc(): String = "女性"
},
UNKNOWN(0) {
override fun getDesc(): String = "未知"
};
// 枚举类的通用方法
fun getCodeDesc(): String {
return "编码:$code,描述:${getDesc()}"
}
}
// 定义枚举类:按钮状态
enum class ButtonState {
NORMAL, PRESSED, DISABLED
}
// 处理按钮状态
fun handleButtonState(state: ButtonState) {
when (state) {
ButtonState.NORMAL -> println("按钮正常")
ButtonState.PRESSED -> println("按钮按下")
ButtonState.DISABLED -> println("按钮禁用")
// 无需else,编译器检查所有常量
}
}
fun main() {
// 调用枚举常量的属性和方法
val male = Gender.MALE
println(male.code) // 输出:1
println(male.getDesc()) // 输出:男性
println(male.getCodeDesc()) // 输出:编码:1,描述:男性
// 遍历所有枚举常量
Gender.values().forEach { println(it.getCodeDesc()) }
// 处理按钮状态
handleButtonState(ButtonState.PRESSED) // 输出:按钮按下
}
实战场景
Android中定义固定的状态常量(如网络请求的请求方式:GET/POST、UI的主题模式:亮色/暗色、弹窗的类型:提示/确认/取消),用枚举类替代const val,让代码更规范、可读性更高。
更多推荐


所有评论(0)