一、章节介绍

在这里插入图片描述

本节课程是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. 编码规范与程序可读性

  • 核心原则:遵循“小驼峰命名法”(变量/函数名首字母小写,如userNamecalculateScore),避免中文符号(如中文逗号、引号),缩进统一(推荐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基础语法核心,覆盖“程序入口-变量-字符串-函数”四大模块,核心要点可概括为:

  1. 主函数是程序唯一入口,语法简化为fun main() { ... }
  2. 变量优先用val(不可变),仅动态值用var(可变),依赖类型推断减少冗余代码;
  3. 字符串模板用$变量${表达式},替代传统拼接;
  4. 函数支持默认参数(省略重复传值)和命名参数(避免顺序错误),参数必须指定类型;
  5. 编码规范(小驼峰、英文符号)和注释语法与Java一致,需严格遵循以提升协作效率。

四、知识点补充

1. 补充知识点(5个核心扩展)

补充知识点 说明与代码示例
可空类型(面试高) 变量后加?表示可空(避免空指针异常),调用时需安全处理(?.?:):
val age: Int? = null
val 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”是企业级开发的核心最佳实践,其价值体现在三个维度:

  1. 线程安全:多线程环境下,不可变变量(val)无需加锁即可安全访问,避免因“多个线程修改同一变量”导致的竞态条件(如计数器错乱)。例如Android开发中,val text = "首页标题"作为TextView的文本,多线程下不会因修改导致UI显示异常。
  2. 代码可维护性:val变量初始化后引用不可变,后续代码无需追踪其值的变化(如var变量可能在多个函数中被修改,排查bug时需逐行查找赋值点)。例如定义val userList: List<User> = api.getUserList(),后续仅能读取列表元素,无法修改列表结构,减少“无意识修改”导致的bug。
  3. 符合函数式编程思想:Kotlin支持函数式编程(如lambda、流操作),不可变是函数式的核心特性之一。例如使用userList.filter { it.age > 18 }时,val保证原列表不被修改,过滤结果生成新列表,符合“无副作用”的函数式原则。

反例:滥用var导致的问题

// 不推荐:用var声明无需修改的变量
var appName = "MyApp"
// 后续代码可能误修改(如他人接手时添加appName = "YourApp"),导致全局配置错乱

// 推荐:用val声明不可变变量
val appName = "MyApp"
// 后续无法修改,避免误操作

3. 编程思想指导:Kotlin的“简洁性与安全性平衡”

Kotlin的设计哲学是“在简化语法的同时,不牺牲安全性”,这一思想贯穿从基础语法到高级特性的所有环节,是程序员编写高质量Kotlin代码的核心指导:

  1. 简洁性:减少样板代码,提升开发效率

    • 类型推断:无需显式声明String name = "Alice",简化为val name = "Alice",减少冗余的类型书写,同时编译器自动校验类型合法性。
    • 函数表达式:用fun getArea(r: Double) = Math.PI * r * r替代“函数体+return”的冗余写法,代码更短且意图明确。
    • 字符串模板:用"用户$name的年龄是$age"替代"用户" + name + "的年龄是" + age,避免拼接时漏加+号或括号导致的语法错误。
  2. 安全性:通过语法约束,规避常见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有什么区别?分别适用于什么场景?

答案

  • 区别:
    1. 可变性:var是可变变量,声明后可多次赋值修改值;val是不可变变量,声明时必须初始化,初始化后引用不可变(引用类型的内部属性可能可变)。
    2. 编译约束:val变量赋值后若再次赋值,会直接编译报错;var无此约束。
  • 适用场景:
    • val:优先使用,适用于值无需修改的场景(如固定配置、初始化后不变的对象引用,如val baseUrl = "https://api.example.com"),提升线程安全和代码可维护性。
    • var:仅用于值需动态修改的场景(如计数器、用户输入后的动态数据,如var count = 0; count++)。

2. 中等题:请解释Kotlin字符串模板的用法,并编写代码示例展示基础用法和复杂表达式用法。

答案
Kotlin字符串模板用于在字符串中嵌入变量或表达式,无需手动拼接,语法简洁且避免拼接错误,核心用法分为两类:

  1. 基础用法:用$变量名直接嵌入单个变量(支持任意数据类型)。
  2. 复杂表达式用法:用${表达式}包裹运算、函数调用、逻辑判断等复杂逻辑。

代码示例:

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函数的默认参数和命名参数有什么作用?请编写代码示例说明如何使用,并解释命名参数的一个核心优势。

答案

  • 作用:
    1. 默认参数:允许函数参数声明时指定默认值,调用时可省略该参数,减少“重载函数”的编写(如无需为“带折扣”和“无折扣”分别写两个函数)。
    2. 命名参数:允许调用函数时指定参数名,无需按形参顺序传递,解决“多同类型参数顺序混淆”的问题。

代码示例:

// 定义带默认参数的函数:计算订单总价(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. 歧义解决方案

针对上述编译报错场景,可通过以下方式消除歧义:

  1. 显式指定参数类型:通过类型转换明确匹配的函数,例如:

    // 显式将b转换为Int,匹配calculate(a: Int, b: Int)
    calculate(5, 3 as Int) // 输出:3(5-2,此处3为示例,实际参数为3时输出5-3=2)
    
  2. 使用命名参数:通过参数名明确指定目标函数(适用于参数名不同或需区分默认参数的场景),例如:

    // 若函数参数名不同,可通过命名参数区分;此处参数名均为b,需结合类型
    calculate(a = 5, b = 3 as Int) // 同上,明确匹配Int类型的重载函数
    
  3. 调整函数设计(推荐):删除冗余的重载或默认参数,从根源避免歧义。例如删除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无法在声明时初始化,但又不想用可空类型?),提供lateinitby 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中实体类的样板代码问题,是开发中最常用的特殊类。

核心特性

  1. 主构造函数必须包含至少一个参数;
  2. 参数建议用val(不可变),若需修改用var
  3. 自动生成的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分支(编译器会检查所有子类)。

核心特性

  1. 密封类本身是抽象类,无法实例化;
  2. 子类可以是普通类、数据类、对象;
  3. 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中的StringUtilsViewUtils),Android开发中高频用于封装View、String、Collection等类的通用方法。

核心特性

  1. 扩展函数的this指代被扩展的类的实例;
  2. 扩展属性无法直接定义字段,需通过get()/set()实现(无幕后字段);
  3. 若原类有同名函数,原类函数优先级更高

代码示例

// 示例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. 函数类型声明:(参数类型1, 参数类型2) -> 返回值类型(无返回值则为() -> Unit);
  2. 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的代码,而非创建匿名内部类,彻底消除性能损耗。

核心特性

  1. 仅用于参数包含函数类型的高阶函数;
  2. noinline修饰不需要内联的函数类型参数;
  3. 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内置的forEachletapply等作用域函数均为内联函数。

8. 智能类型转换与安全类型转换

核心说明

针对Kotlin的类型检查与转换,提供智能类型转换(is/!is)安全类型转换(as?),替代Java的instanceof+强制类型转换,彻底规避ClassCastException(类型转换异常),是类型处理的核心进阶语法。

核心特性

  1. 智能类型转换:用is判断类型后,编译器自动将对象转换为该类型,无需手动转换;
  2. 安全类型转换:用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
}

实战场景

  1. 类委托:Android中实现自定义集合、自定义View的接口方法,复用系统类的实现;
  2. 属性委托:封装Android的SharedPreferencesViewModel的状态属性,简化读写逻辑;Kotlin内置的by lazy也是属性委托的一种。

10. 枚举类(enum class)

核心说明

用于表示有限的、固定的常量集合(如性别:男/女、季节:春/夏/秋/冬、按钮状态:正常/按下/禁用),相比Java的枚举,Kotlin的枚举类支持实现接口、定义属性和方法,更灵活,是替代常量定义(const val)的更规范方案。

核心特性

  1. 枚举常量是枚举类的实例,直接通过枚举类.常量调用;
  2. 可定义属性和方法,每个常量可单独传参;
  3. 支持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,让代码更规范、可读性更高。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐