Kotlin基础语法详解

变量声明与类型系统

Kotlin的变量声明体系是其语法简洁性与安全性的基础,核心在于通过val(不可变)与var(可变)的严格区分,引导开发者采用不可变编程思想。与Java相比,Kotlin省略了显式的访问修饰符(默认public)和类型声明(支持自动推断),显著减少了冗余代码[4]。

不可变变量(val)
val声明的变量在初始化后无法重新赋值,对应Java中的final变量。这种设计强制约束状态变化,减少并发场景下的数据竞争风险,是Kotlin推荐的变量声明方式。其语法格式为:

kotlin
复制代码
// 显式指定类型(推荐用于复杂场景,提升可读性)
val userName: String = "Kotlin"
// 类型自动推断(基础类型或简单场景)
val userAge = 30 // 推断为Int类型
// 编译错误:val变量不可二次赋值
userName = "NewName" // Error: Val cannot be reassigned

可变变量(var)
var声明的变量允许后续修改,但其使用需谨慎,仅在明确需要状态变化时采用(如计数器、动态配置)。Android开发中过度使用var可能导致Activity/Fragment的状态管理混乱,增加调试难度。示例:

kotlin
复制代码
// 可变变量声明
var score: Int = 0
score += 10 // 允许修改:score变为10
// 类型推断与可空性结合
var nullableName: String? = "Alice" // 可空类型(String?)
nullableName = null // 合法:可空类型允许赋值null

延迟初始化(lateinit var)
对于非空类型但无法在声明时初始化的变量(如依赖注入对象、Activity中的视图),Kotlin提供lateinit关键字延迟初始化。需注意:lateinit仅支持var且不能用于基本类型(如Int,需用Int?Delegates.notNull()替代)[4]。示例:

kotlin
复制代码
class MainActivity : AppCompatActivity() {
    // 延迟初始化视图(onCreate中赋值)
    private lateinit var recyclerView: RecyclerView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        recyclerView = findViewById(R.id.recycler_view) // 必须初始化,否则抛异常
    }
}

空安全类型系统
Kotlin通过类型系统强制区分非空类型(如String)与可空类型(如String?),从编译期避免NullPointerException(NPE)。Android官方数据显示,该特性可使应用崩溃率降低20%[5]。核心语法包括:

  • 可空标记(?):变量后加?表示允许为null
  • 安全调用(?.):调用前检查对象是否为null,若为null则返回null
  • 非空断言(!!):强制认为对象非空,若为null则抛出NPE(谨慎使用)
kotlin
复制代码
// 可空类型变量
val nullableString: String? = null
// 安全调用:若nullableString为null,返回null而非抛出NPE
val length = nullableString?.length // 结果为null(Int?类型)
// 非空断言:仅在确定对象非空时使用
val forcedLength = nullableString!!.length // 抛出NullPointerException

最佳实践

  1. 优先使用val:80%以上的变量场景可通过val实现,仅在确需状态变化时使用var
  2. 避免lateinit滥用:替代方案包括by lazy(懒加载)或可空类型+空判断
  3. 拒绝!!断言:90%的!!场景可通过安全调用(?.)或Elvis运算符(?:)优化
    kotlin
    复制代码
    // Elvis运算符示例:为空时返回默认值
    val safeLength = nullableString?.length ?: -1 // 结果为-1

函数定义与调用

Kotlin的函数体系通过灵活的语法设计实现了代码精简与可读性的平衡,核心特性包括表达式体函数、命名参数、默认参数等,显著优于Java的函数定义方式。

函数基本结构
函数声明以fun关键字开头, followed by 函数名、参数列表(参数名: 类型)、返回类型(:后指定,无返回值可省略或显式Unit)和函数体。示例:

kotlin
复制代码
// 完整函数定义(含返回类型和代码块)
fun add(a: Int, b: Int): Int {
    return a + b
}
// 无返回值函数(Unit可省略)
fun printSum(a: Int, b: Int): Unit {
    println("Sum: ${a + b}")
}
// 简化无返回值函数
fun log(message: String) {
    println("[LOG] $message")
}

单表达式函数
对于仅含单个表达式的函数,Kotlin允许省略花括号和return关键字,直接用=连接参数与表达式结果。这种语法使代码行数减少50%以上,尤其适合工具类函数或简单计算[6]。示例:

kotlin
复制代码
// 等价于add函数的单表达式形式
fun add(a: Int, b: Int) = a + b
// 带条件判断的单表达式函数
fun getAbsoluteValue(num: Int) = if (num >= 0) num else -num
// 复杂表达式(类型自动推断为Boolean)
fun isEven(num: Int) = num % 2 == 0

命名参数与默认参数
Kotlin支持调用函数时显式指定参数名,解决了Java中多参数函数调用时的"参数顺序依赖"问题。结合默认参数,可实现"可选参数"效果,避免Java中重载函数的冗余定义。示例:

kotlin
复制代码
// 带默认参数的函数
fun greet(name: String, title: String = "Mr.", suffix: String = "!"): String {
    return "$title $name$suffix"
}

// 调用方式对比
greet("Smith") // 使用默认参数:"Mr. Smith!"
greet("Alice", "Ms.") // 按位置传参:"Ms. Alice!"
greet(name = "Bob", suffix = "!!!") // 命名参数跳过默认值:"Mr. Bob!!!"
greet(suffix = "?", name = "Charlie") // 命名参数无序传递:"Mr. Charlie?"

空安全函数设计
函数参数和返回值的可空性需显式声明,确保调用方正确处理空值。例如,将字符串转换为整数时,toIntOrNull()返回Int?类型,强制调用方处理转换失败场景[7]。示例:

kotlin
复制代码
// 可空返回值函数
fun stringToInt(str: String): Int? {
    return str.toIntOrNull() // 失败时返回null
}

// 调用方必须处理空值
val number = stringToInt("123") // Int?类型
if (number != null) {
    println("Valid number: $number")
} else {
    println("Invalid input")
}

函数设计最佳实践

  • 单职责原则:函数长度控制在10行以内,复杂逻辑拆分为多个小函数
  • 表达式体优先:单行逻辑强制使用单表达式函数(fun f() = ...
  • 默认参数位置:将默认参数放在参数列表末尾,避免调用歧义
  • 空安全返回值:优先返回非空类型,确需返回null时文档注明空值条件

控制流与字符串模板

Kotlin的控制流结构在Java基础上进行了革命性优化,when表达式替代了传统switch-case,支持任意类型匹配、范围判断和表达式返回值;字符串模板则通过简洁的插值语法,避免了Java中字符串拼接的繁琐。

when表达式
when是Kotlin最具特色的控制流结构,相比Java的switch-case具有三大优势:

  1. 支持任意类型匹配:不仅限于整数/枚举,可匹配字符串、对象、范围等
  2. 自动省略break:无需显式break,避免"穿透执行"错误
  3. 作为表达式返回值:直接返回分支结果,简化赋值逻辑

基础用法示例:

kotlin
复制代码
// 匹配具体值(等价于Java switch)
fun getDayOfWeek(day: Int): String = when (day) {
    1 -> "Monday"
    2 -> "Tuesday"
    3, 4 -> "Midweek" // 多值匹配(3或4)
    in 5..7 -> "Weekend" // 范围匹配(5-7)
    else -> "Invalid day" // 必须覆盖所有情况
}

// 条件判断模式(无参数when)
fun getGreeting(hour: Int): String = when {
    hour in 6..11 -> "Good morning" // 6:00-11:59
    hour in 12..17 -> "Good afternoon" // 12:00-17:59
    hour < 6 || hour > 23 -> "Good night"
    else -> "Good evening"
}

// 对象类型匹配(高级用法)
fun processData(data: Any): String = when (data) {
    is String -> "String length: ${data.length}"
    is Int -> "Int value: $data"
    is List<*> -> "List size: ${data.size}"
    else -> "Unknown type"
}

循环结构
Kotlin保留了forwhile循环,但通过区间表达式迭代器扩展增强了易用性。其中repeat(n)函数是快速实现固定次数循环的语法糖,特别适合初始化逻辑或测试场景[8]。示例:

kotlin
复制代码
// 传统for循环(迭代区间)
for (i in 1..5) { // 闭区间:1,2,3,4,5
    print(i)
}

// 步长控制(间隔2)
for (i in 10 downTo 1 step 2) { // 10,8,6,4,2
    print(i)
}

// repeat函数(固定次数循环)
repeat(3) { index -> // 索引从0开始:0,1,2
    println("Loop $index: Hello")
}

// 集合迭代
val fruits = listOf("Apple", "Banana", "Cherry")
for ((index, fruit) in fruits.withIndex()) {
    println("$index: $fruit") // 输出"0: Apple", "1: Banana", "2: Cherry"
}

字符串模板
Kotlin通过$符号实现字符串插值,避免了Java中+拼接的低效与冗长。基础语法支持直接插入变量,复杂表达式则需用${}包裹;Kotlin 2.2进一步增强为多美元插值,允许在单个模板中嵌套对象属性或函数调用[9]。

基础用法与常见错误:

kotlin
复制代码
// 基础变量插值
val name = "Alice"
val age = 25
val basicInfo = "Name: $name, Age: $age" // 结果:"Name: Alice, Age: 25"

// 复杂表达式插值(需用${})
val price = 19.99
val quantity = 3
val total = "Total: $${price * quantity}" // 错误:$后直接跟表达式需用{}
val correctTotal = "Total: $${(price * quantity).toInt()}" // 正确:$${59}($符号需转义)

// 对象属性插值(Kotlin 2.2多美元特性)
data class User(val name: String, val age: Int)
val user = User("Bob", 30)
val userInfo = "User ${user.name} (${user.age})" // 结果:"User Bob (30)"

// 常见错误:缺少变量名
val invalidTemplate = "Value: $" // 编译警告:Unresolved reference: (需指定变量)
val validTemplate = "Value: ${price}" // 正确:"Value: 19.99"

控制流与模板最佳实践

  • when表达式完整性:作为返回值时必须覆盖所有情况(添加else分支),避免编译错误[10]
  • 字符串模板性能:频繁拼接长字符串时,优先使用StringBuilderbuildString函数
    kotlin
    复制代码
    val longText = buildString {
        append("Hello, ")
        append(name)
        append("! Your score is ")
        append(score)
    }
  • 范围判断优先用in:替代>=<=,如hour in 9..18hour >=9 && hour <=18更直观

空安全与异常处理

空安全是Kotlin对Java的重大改进,通过类型系统强制区分可空与非空类型,从源头减少NPE。其核心机制包括可空类型标记(?)、安全调用(?.)、非空断言(!!)和 Elvis运算符(?:)[11]。

可空类型操作

kotlin
复制代码
// 安全调用链(任意环节为null则整体返回null)
val street = user?.address?.street // 若user或address为null,结果为null

// Elvis运算符(为空时返回默认值)
val userName = nullableUser?.name ?: "Guest" // 等价于if (nullableUser != null) nullableUser.name else "Guest"

// 非空断言(强制非空,风险操作)
val nonNullName = nullableName!! // 若nullableName为null,抛出NullPointerException

自定义异常
Kotlin的异常体系与Java兼容,通过继承ExceptionRuntimeException实现自定义异常。语法简洁,无需显式throws声明,异常传播更灵活[12]。示例:

kotlin
复制代码
// 自定义检查型异常(编译期检查)
class InvalidInputException(message: String) : Exception(message)

// 自定义运行时异常(运行期抛出)
class NetworkException(message: String) : RuntimeException(message)

// 抛出与捕获异常
fun validateInput(input: String) {
    if (input.isEmpty()) {
        throw InvalidInputException("Input cannot be empty")
    }
}

fun main() {
    try {
        validateInput("")
    } catch (e: InvalidInputException) {
        println("Error: ${e.message}") // 输出:Error: Input cannot be empty
    } finally {
        println("Validation completed") // 始终执行
    }
}

语法常见错误与规避

Kotlin编译器虽以严格著称,但仍存在易犯的语法错误,需特别注意以下场景:

1. 变量作用域问题

kotlin
复制代码
fun calculate() {
    if (condition) {
        val result = 10 // 局部变量仅在if块内可见
    }
    println(result) // 编译错误:Unresolved reference: result
}

规避:将变量声明提升至外层作用域,或使用表达式返回值。

2. when表达式缺少else分支

kotlin
复制代码
fun getGrade(score: Int): Char = when (score) {
    in 90..100 -> 'A'
    in 80..89 -> 'B'
    // 缺少else分支,编译错误:'when' expression must be exhaustive
}

规避:添加else分支处理默认情况,或确保覆盖所有可能值(如枚举匹配)。

3. 可空类型直接使用

kotlin
复制代码
fun printLength(str: String?) {
    println(str.length) // 编译错误:Only safe (?.) or non-null asserted (!!.) calls are allowed on nullable receiver
}

规避:使用安全调用(str?.length)或非空断言(str!!.length,谨慎使用)。

4. 字符串模板转义错误

kotlin
复制代码
val price = "$$19.99" // 错误:$符号未转义,编译器尝试解析变量19.99
val correctPrice = "$${19.99}" // 正确:第一个$转义,输出"$19.99"

规避:使用$转义$符号,复杂表达式用${}包裹。

通过以上语法规则的系统学习,开发者可充分利用Kotlin的简洁性与安全性,显著提升Android项目的开发效率与代码质量。建议结合官方中文文档深入实践,逐步掌握高级特性如作用域函数、委托模式等[13]。