在掌握了 Kotlin 的基础语法后,深入学习其进阶特性将帮助你编写更简洁、高效且易维护的代码。本课将详细讲解 Kotlin 中几个重要的进阶特性,包括扩展函数与属性、委托、协程、枚举类和注解。
1. 扩展函数与扩展属性
Kotlin 允许我们在不修改原有类代码的情况下,为其添加新的函数和属性,这就是扩展(Extensions)特性。
1.1 扩展函数的定义
扩展函数的定义格式如下:
Kotlin
fun 接收者类型.函数名(参数列表): 返回值类型 {
// 函数体
}
其中,"接收者类型" 是我们要扩展的类,在函数体内可以使用 this
关键字引用接收者对象。
1.2 扩展函数的应用
扩展函数特别适合为系统类添加自定义方法,例如为 String 类添加一个判断是否为邮箱的方法:
Kotlin
// 为 String 类添加扩展函数
fun String.isEmail(): Boolean {
val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$".toRegex()
return matches(emailRegex)
}
// 使用扩展函数
fun main() {
val email = "test@example.com"
println(email.isEmail()) // 输出: true
val notEmail = "test.example.com"
println(notEmail.isEmail()) // 输出: false
}
扩展函数并不会真正修改目标类,它只是在编译期为目标类添加了一个可调用的函数,在字节码层面,它会被编译为一个接受目标类型作为参数的静态函数。
1.3 扩展属性的定义与使用
除了函数,我们还可以为类添加扩展属性:
Kotlin
// 为 String 类添加扩展属性
val String.firstChar: Char?
get() = if (isEmpty()) null else this[0]
// 使用扩展属性
fun main() {
val str = "Kotlin"
println(str.firstChar) // 输出: K
val emptyStr = ""
println(emptyStr.firstChar) // 输出: null
}
扩展属性不能有初始化器,必须通过 getter/setter 来定义,因为它没有实际的字段存储。
2. 委托
委托(Delegation)是一种设计模式,它允许将一个类的部分功能委托给另一个类。Kotlin 原生支持委托模式,通过 by
关键字可以轻松实现。
2.1 委托模式的概念
委托模式的核心思想是:一个对象将部分职责委托给另一个对象来完成,从而实现对象之间的协作。这种模式可以替代继承,实现更灵活的代码复用。
2.2 类委托
类委托通过 by
关键字实现,允许一个类将接口的实现委托给另一个对象:
Kotlin
// 定义接口
interface Printer {
fun print(text: String)
}
// 实现接口的委托类
class ConsolePrinter : Printer {
override fun print(text: String) {
println("打印: $text")
}
}
// 使用委托的类
class DocumentPrinter(printer: Printer) : Printer by printer {
// 可以添加额外的方法或重写委托的方法
fun printDocument(title: String, content: String) {
print("标题: $title")
print("内容: $content")
}
}
fun main() {
val consolePrinter = ConsolePrinter()
val docPrinter = DocumentPrinter(consolePrinter)
docPrinter.print("直接打印文本") // 委托给 ConsolePrinter 实现
docPrinter.printDocument(" Kotlin 委托", "类委托示例")
}
在这个例子中,DocumentPrinter
类通过 by printer
语法将 Printer
接口的实现委托给了 printer
对象。
2.3 属性委托
属性委托允许将属性的 getter/setter 逻辑委托给一个专门的对象。Kotlin 标准库提供了一些常用的委托实现:
2.3.1 by lazy(延迟初始化)
lazy
委托用于实现属性的延迟初始化,属性值会在第一次访问时计算:
Kotlin
val expensiveResource: String by lazy {
println("初始化资源...")
"这是一个昂贵的资源"
}
fun main() {
println("开始")
println(expensiveResource) // 首次访问,触发初始化
println(expensiveResource) // 直接使用已初始化的值
}
输出结果:
plaintext
开始
初始化资源...
这是一个昂贵的资源
这是一个昂贵的资源
2.3.2 Delegates.observable(可观察属性)
Delegates.observable
可以监听属性值的变化:
Kotlin
import kotlin.properties.Delegates
var username: String by Delegates.observable("默认值") {
property, oldValue, newValue ->
println("${property.name} 从 $oldValue 变为 $newValue")
}
fun main() {
username = "Alice"
username = "Bob"
}
输出结果:
plaintext
username 从 默认值 变为 Alice
username 从 Alice 变为 Bob
2.3.3 自定义委托
我们也可以实现自己的属性委托,只需要实现 ReadOnlyProperty
(只读属性)或 ReadWriteProperty
(可读写属性)接口:
Kotlin
import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
// 自定义委托:确保值在 0-100 之间
class RangeValidator(private val min: Int, private val max: Int) :
ReadWriteProperty<Any?, Int> {
private var value: Int = min
override fun getValue(thisRef: Any?, property: KProperty<*>): Int {
return value
}
override fun setValue(thisRef: Any?, property: KProperty<*>, value: Int) {
if (value in min..max) {
this.value = value
} else {
throw IllegalArgumentException("值必须在 $min 到 $max 之间")
}
}
}
// 使用自定义委托
class Student {
var score: Int by RangeValidator(0, 100)
}
fun main() {
val student = Student()
student.score = 90 // 有效
println(student.score) // 输出: 90
try {
student.score = 150 // 无效,会抛出异常
} catch (e: IllegalArgumentException) {
println(e.message) // 输出: 值必须在 0 到 100 之间
}
}
3. 协程
协程(Coroutines)是 Kotlin 中处理异步编程的重要特性,它可以看作是轻量级的线程,能够在单个线程内实现多个任务的切换执行。
3.1 协程的概念
协程与线程的主要区别在于:
- 线程是操作系统级别的资源,创建和切换成本较高
- 协程是用户态的,由 Kotlin 运行时管理,创建和切换成本极低
- 一个线程可以运行多个协程
协程的核心优势是能够以同步的代码风格编写异步操作,避免了回调地狱(Callback Hell)。
3.2 协程的基础使用
要使用协程,需要添加依赖:
gradle
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4'
}
基本使用示例:
Kotlin
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() {
println("程序开始")
// 启动一个协程
GlobalScope.launch {
delay(1000) // 非阻塞延迟 1 秒(协程特有的挂起函数)
println("协程执行")
}
Thread.sleep(1500) // 让主线程等待协程执行完毕
println("程序结束")
}
runBlocking
可以创建一个阻塞当前线程的协程作用域,常用于桥接普通代码和协程代码:
Kotlin
fun main() = runBlocking {
println("程序开始")
launch {
delay(1000)
println("协程执行")
}
println("程序结束")
// runBlocking 会等待所有子协程执行完毕才会结束
}
3.3 协程的挂起函数
使用 suspend
关键字可以定义挂起函数,挂起函数只能在协程或其他挂起函数中调用:
Kotlin
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
// 定义挂起函数
suspend fun fetchData(): String {
delay(1000) // 模拟网络请求
return "从服务器获取的数据"
}
// 另一个挂起函数
suspend fun processData(): String {
delay(500) // 模拟数据处理
return "处理后的数据"
}
fun main() = runBlocking {
val data = fetchData()
println(data)
val processedData = processData()
println(processedData)
}
挂起函数的特点是在执行到挂起点(如 delay
)时,会暂停当前协程的执行,让出线程给其他协程,当挂起操作完成后,协程会在适当的时候恢复执行。
3.4 协程的作用域与取消
协程应该在合适的作用域(CoroutineScope)中启动,以便管理其生命周期。除了 GlobalScope
,我们还可以创建自己的作用域:
Kotlin
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
// 创建一个协程作用域
val scope = CoroutineScope(Dispatchers.Default)
val job = scope.launch {
repeat(10) { i ->
println("正在执行: $i")
delay(500)
}
}
delay(1500) // 等待 1.5 秒
job.cancel() // 取消协程
job.join() // 等待协程完全取消
println("协程已取消")
scope.cancel() // 取消整个作用域
}
输出结果:
plaintext
正在执行: 0
正在执行: 1
正在执行: 2
协程已取消
4. 枚举类
枚举类(Enum Classes)用于表示固定数量的常量集合,如星期几、方向、状态等。
4.1 enum class 定义枚举
Kotlin
// 简单的枚举类
enum class Direction {
NORTH, SOUTH, EAST, WEST
}
4.2 枚举常量的属性与方法
枚举常量可以有自己的属性和方法:
Kotlin
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF); // 注意这里的分号,当枚举有成员函数时必须添加
fun isDark(): Boolean {
// 简单判断:RGB 值较低的认为是深色
return rgb < 0x808080
}
}
fun main() {
println(Color.RED.rgb) // 输出: 16711680
println(Color.BLUE.isDark()) // 输出: true
}
枚举类还提供了一些内置方法:
valueOf()
: 根据名称获取枚举常量values()
: 返回所有枚举常量的数组
Kotlin
fun main() {
val color = Color.valueOf("GREEN")
println(color) // 输出: GREEN
for (c in Color.values()) {
println("$c: ${c.rgb}")
}
}
4.3 枚举的应用场景
枚举类适用于表示有限的、固定的选项集合:
- 状态标识:表示对象的不同状态
Kotlin
enum class OrderStatus {
CREATED, PAID, SHIPPED, DELIVERED, CANCELLED
}
- 选项列表:表示 UI 中的下拉选项等
Kotlin
enum class SortOption(val displayName: String) {
PRICE_ASC("价格从低到高"),
PRICE_DESC("价格从高到低"),
NEWEST("最新上架"),
POPULAR("最受欢迎")
}
- 替代魔法数字:使代码更具可读性
Kotlin
// 不推荐:使用魔法数字
if (status == 2) { ... }
// 推荐:使用枚举
if (status == OrderStatus.SHIPPED) { ... }
5. 注解
注解(Annotations)是一种为代码添加元数据的方式,它不会直接影响代码的执行,但可以被编译器或其他工具使用。
5.1 注解的定义
使用 annotation class
关键字定义注解:
Kotlin
// 定义一个简单的注解
annotation class MyAnnotation
// 带参数的注解
annotation class Todo(val description: String)
// 限制注解的使用范围
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY)
annotation class DeprecatedFeature(val replacement: String)
@Target
注解用于指定注解可以应用的元素类型,常见的目标包括:
CLASS
: 类、接口、枚举等FUNCTION
: 函数PROPERTY
: 属性PARAMETER
: 函数参数TYPE
: 类型
还可以使用 @Retention
注解指定注解的保留策略:
SOURCE
: 只在源代码中保留,编译后丢弃BINARY
: 保留到编译后的字节码中,但不会被虚拟机加载RUNTIME
: 保留到运行时,可以通过反射获取
Kotlin
import kotlin.annotation.RetentionPolicy
@Retention(AnnotationRetention.RUNTIME)
annotation class DebugInfo
5.2 常用内置注解
Kotlin 提供了一些常用的内置注解:
@JvmStatic
:用于 companion object 中的函数,使其编译为 Java 中的静态方法
Kotlin
class Utils {
companion object {
@JvmStatic
fun doSomething() {
// ...
}
}
}
@Deprecated
:标记已过时的代码
Kotlin
@Deprecated(
message = "此方法已过时,请使用 newMethod()",
replaceWith = ReplaceWith("newMethod()"),
level = DeprecationLevel.WARNING
)
fun oldMethod() {
// ...
}
@Nullable
和@NonNull
:标记变量或参数是否可以为 null,常用于与 Java 互操作
Kotlin
import org.jetbrains.annotations.Nullable
fun processData(@Nullable data: String?) {
// ...
}
@Suppress
:抑制编译器警告
Kotlin
@Suppress("UNCHECKED_CAST")
fun unsafeCast(obj: Any): String {
return obj as String
}
5.3 注解的应用
注解在实际开发中有很多应用场景:
- 代码标记 :如
@Deprecated
标记过时代码,@Test
标记测试方法等 - 代码生成:许多框架使用注解来生成代码,如 Dagger、Room 等
Kotlin
// Room 数据库框架使用注解
@Entity(tableName = "users")
data class User(
@PrimaryKey val id: Int,
@ColumnInfo(name = "user_name") val name: String
)
- 运行时处理:通过反射在运行时获取注解信息,实现特定逻辑
确保项目中添加了 Kotlin 反射依赖:
gradle
dependencies {
implementation "org.jetbrains.kotlin:kotlin-reflect:1.8.0"
}
代码:
Kotlin
import kotlin.reflect.KFunction
import kotlin.reflect.full.findAnnotation
import kotlin.reflect.full.memberFunctions
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.FUNCTION)
annotation class LogExecution
// 使用注解
class Service {
@LogExecution
fun performTask() {
// ...
}
}
// 处理注解的工具类
object AnnotationProcessor {
fun process(obj: Any) {
obj::class.memberFunctions.forEach { function ->
if (function.findAnnotation<LogExecution>() != null) {
println("执行方法: ${function.name}")
// 可以在这里添加日志记录、性能监控等逻辑
}
}
}
}
fun main() {
val service = Service()
AnnotationProcessor.process(service)
service.performTask()
}
6. 其他实用进阶特性
6.1 解构声明
解构声明允许将一个对象的多个属性同时赋值给多个变量:
Kotlin
data class Person(val name: String, val age: Int)
fun main() {
val person = Person("Alice", 30)
// 解构声明
val (name, age) = person
println("$name 今年 $age 岁") // 输出: Alice 今年 30 岁
// 对集合使用解构
val list = listOf("a", "b", "c")
val (first, second) = list
println("$first, $second") // 输出: a, b
}
6.2 密封类
密封类(Sealed Classes)用于表示受限的类层次结构,适合用于定义枚举类的扩展:
Kotlin
sealed class Result<out T>
data class Success<out T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
fun fetchData(): Result<String> {
return try {
// 模拟网络请求
Success("获取的数据")
} catch (e: Exception) {
Error(e.message ?: "未知错误")
}
}
fun main() {
val result = fetchData()
// 使用 when 表达式处理密封类,编译器会检查所有可能的情况
when (result) {
is Success -> println("成功: ${result.data}")
is Error -> println("失败: ${result.message}")
}
}