Kotlin 扩展:不修改源码,给类新增功能(新手易懂版)
Kotlin 的扩展功能是个超实用的特性,它能让你在不修改原类代码、不使用继承的前提下,给任意类(包括系统类、第三方库的类)新增函数和属性。扩展后的功能调用起来和类的原生成员一模一样。
一、核心概念:什么是扩展?
扩展的本质是编译器的语法糖——它并没有真正修改原类,只是在调用时,编译器帮你把扩展函数/属性转成了普通的函数调用。
举个简单例子:你想给 String 类加一个“截断过长字符串”的功能,不用改 String 的源码,直接写一个扩展函数就行。
扩展主要分两种:
-
扩展函数:给类新增方法 -
扩展属性:给类新增属性
二、基础用法1:扩展函数
扩展函数是最常用的扩展类型,语法很简单:**fun 接收者类型.函数名(参数): 返回值 { 实现 }**。
-
接收者类型:你要扩展的类(比如 String、List) -
this关键字:在扩展函数里,this指代接收者的实例
1. 给普通类写扩展函数
需求:给 String 类加一个 truncate 函数,过长时自动截断并加省略号。
// 扩展函数:接收者类型是 String
fun String.truncate(maxLength: Int): String {
// this 就是调用这个函数的 String 实例
returnif (this.length <= maxLength) thiselsethis.take(maxLength - 3) + "..."
}
funmain() {
val longStr = "我是一个很长很长的字符串"
// 调用扩展函数,和调用原生函数一样
println(longStr.truncate(8)) // 输出:我是一个很长...
}
2. 给接口写扩展函数
给接口写扩展函数,所有实现该接口的类都会自动拥有这个功能,不用重复写代码。
// 定义一个用户接口
interfaceUser{
val name: String
val age: Int
}
// 给接口写扩展函数
fun User.introduce(): String {
return"我叫${this.name},今年${this.age}岁"
}
// 实现接口的类,自动拥有 introduce 函数
classStudent(overrideval name: String, overrideval age: Int) : User
funmain() {
val student = Student("张三", 20)
println(student.introduce()) // 输出:我叫张三,今年20岁
}
3. 泛型扩展函数
扩展函数也可以是泛型的,能适配任意类型的类,通用性更强。需求:给 List 加一个 getFirstAndLast 函数,返回第一个和最后一个元素的 Pair。
// 泛型扩展函数:接收者类型是 List<T>
fun<T> List<T>.getFirstAndLast(): Pair<T, T> {
require(this.isNotEmpty()) { "列表不能为空" }
returnthis.first() to this.last()
}
funmain() {
val numList = listOf(1, 2, 3, 4, 5)
val strList = listOf("A", "B", "C")
println(numList.getFirstAndLast()) // 输出:(1, 5)
println(strList.getFirstAndLast()) // 输出:(A, C)
}
4. 可空接收者的扩展函数
可以给可空类型写扩展函数,这样即使接收者是 null,也能安全调用,不用提前判空。
// 接收者类型是 String?,支持 null 调用
fun String?.safeToString(): String {
// this 可以是 null,直接判断
returnthis ?: "null 值"
}
funmain() {
val str1: String? = "Hello"
val str2: String? = null
println(str1.safeToString()) // 输出:Hello
println(str2.safeToString()) // 输出:null 值
}
三、基础用法2:扩展属性
除了函数,你还能给类新增扩展属性,语法是:**val/var 接收者类型.属性名: 类型 { get() { ... } }**。
关键注意点
-
扩展属性不能有后备字段(因为没有修改原类的内存结构),所以必须手动写 get方法 -
可以定义 var类型的扩展属性,但需要同时写get和set方法
1. 只读扩展属性(val)
需求:给 User 类加一个 isAdult 属性,判断是否成年。
dataclassUser(val name: String, val age: Int)
// 扩展属性:只读属性,只需要 get 方法
val User.isAdult: Boolean
get() = this.age >= 18
funmain() {
val user1 = User("张三", 20)
val user2 = User("李四", 17)
println(user1.isAdult) // 输出:true
println(user2.isAdult) // 输出:false
}
2. 可写扩展属性(var)
需求:给 House 类加一个 houseNumber 属性,用 Map 存储编号。
dataclassHouse(val street: String)
// 用 Map 存储每个 House 的编号(模拟后备字段)
privateval houseNumberMap = mutableMapOf<House, Int>()
// 可写扩展属性:需要 get 和 set 方法
var House.number: Int
get() = houseNumberMap[this] ?: 1// 没有编号默认返回1
set(value) {
houseNumberMap[this] = value // 把编号存到 Map 里
}
funmain() {
val house = House("幸福路")
println(house.number) // 输出:1(默认值)
house.number = 101
println(house.number) // 输出:101
}
四、重要特性:扩展的优先级与解析规则
1. 成员函数优先于扩展函数
如果类的原生成员函数和扩展函数同名、参数也一样,调用时会优先用成员函数。
classExample{
// 原生成员函数
funprintInfo() {
println("我是成员函数")
}
}
// 同名的扩展函数
fun Example.printInfo() {
println("我是扩展函数")
}
funmain() {
Example().printInfo() // 输出:我是成员函数
}
2. 扩展函数是静态解析的
扩展函数的调用,是根据编译时的接收者类型决定的,和运行时的实际类型无关(这和类的多态不一样)。
openclassShape
classCircle : Shape()
// 给 Shape 写扩展函数
fun Shape.getName() = "Shape"
// 给 Circle 写扩展函数
fun Circle.getName() = "Circle"
// 函数参数的类型是 Shape
funprintName(shape: Shape) {
println(shape.getName())
}
funmain() {
// 实际传入的是 Circle 实例,但编译时类型是 Shape
printName(Circle()) // 输出:Shape
}
五、进阶用法:伴生对象扩展
如果一个类有伴生对象,你可以给伴生对象写扩展函数/属性,直接通过类名调用,不用创建实例。
classLogger{
// 定义伴生对象
companionobject {}
}
// 给伴生对象写扩展函数
fun Logger.Companion.log(message: String) {
println("[日志] $message")
}
funmain() {
// 直接通过类名调用扩展函数
Logger.log("程序启动了") // 输出:[日志] 程序启动了
}
六、高级玩法:在类内部写扩展
你可以在一个类的内部,给另一个类写扩展函数,这个扩展函数只能在当前类的内部使用。 这种扩展有两个“接收者”:
-
调度接收者:当前类的实例 -
扩展接收者:被扩展类的实例
classHost(val hostname: String)
classConnection(val host: Host, val port: Int) {
// 在 Connection 内部,给 Host 写扩展函数
fun Host.printConnection() {
// 直接访问 Host 的属性(扩展接收者)
println(hostname)
// 直接访问 Connection 的属性(调度接收者)
println(port)
}
funconnect() {
// 调用 Host 的扩展函数
host.printConnection()
}
}
funmain() {
Connection(Host("kotlin.com"), 8080).connect()
// 输出:
// kotlin.com
// 8080
// 外部无法调用 printConnection 函数
// Host("test.com").printConnection() // 报错
}
七、新手避坑要点
-
扩展不会真正修改原类:扩展只是编译器的语法糖,原类的字节码没有任何变化。 -
扩展属性没有后备字段:必须手动写 get/set方法,不能用field关键字。 -
扩展不能访问原类的私有成员:扩展函数只能访问原类的 public成员。 -
避免滥用扩展:不要给一个类加太多不相关的扩展,否则会让代码难以维护。
八、扩展的适用场景
-
给系统类加功能:比如给 String、List加业务相关的工具函数。 -
优化第三方库的 API:第三方库的类不能修改,用扩展包装成更易用的接口。 -
避免工具类泛滥:不用写 StringUtils、ListUtils这种工具类,直接给对应类加扩展。
扩展是 Kotlin 提升代码简洁性的核心特性之一,合理使用能让你的代码更优雅、更易读!
Kotlin 扩展 高频实用示例(新手友好·可直接复用)
这份示例汇总了日常开发中最常用的扩展场景,涵盖字符串、集合、可空类型、自定义类等,代码可直接复制到项目中使用,大幅提升开发效率。
一、 字符串(String)扩展(高频中的高频)
字符串处理是业务开发的刚需,这些扩展能简化空判断、格式处理、校验等逻辑。
1. 安全判空与默认值
/**
* 字符串为空(包括空白字符)时返回默认值
*/
fun String?.orDefault(defaultValue: String = "默认值"): String {
returnthis?.trim()?.takeIf { it.isNotEmpty() } ?: defaultValue
}
// 调用示例
funmain() {
val str1: String? = null
val str2 = " "
val str3 = "Hello Kotlin"
println(str1.orDefault()) // 输出:默认值
println(str2.orDefault("空字符串")) // 输出:空字符串
println(str3.orDefault()) // 输出:Hello Kotlin
}
2. 手机号格式处理(脱敏)
/**
* 手机号脱敏:138****1234
*/
fun String.desensitizePhone(): String {
if (this.length != 11) returnthis// 非11位直接返回原字符串
returnthis.replaceRange(3, 7, "****")
}
// 调用示例
funmain() {
val phone = "13812345678"
println(phone.desensitizePhone()) // 输出:138****5678
}
3. 是否为纯数字
/**
* 判断字符串是否为纯数字(包括整数、小数)
*/
fun String.isNumber(): Boolean {
val regex = "^[0-9]+(\\.[0-9]+)?$".toRegex()
returnthis.matches(regex)
}
// 调用示例
funmain() {
println("123".isNumber()) // 输出:true
println("123.45".isNumber()) // 输出:true
println("123a".isNumber()) // 输出:false
}
4. 截断过长字符串(带省略号)
/**
* 截断过长字符串,超过最大长度时添加省略号
* @param maxLength 最大长度(包含省略号)
*/
fun String.truncate(maxLength: Int): String {
require(maxLength >= 3) { "最大长度不能小于3" } // 省略号占3位
returnif (this.length <= maxLength) thiselsethis.take(maxLength - 3) + "..."
}
// 调用示例
funmain() {
val longStr = "Kotlin 扩展功能非常实用且强大"
println(longStr.truncate(10)) // 输出:Kotlin 扩展功...
}
二、 集合(List/MutableList)扩展
集合操作在数据处理中频繁出现,这些扩展能简化数据筛选、提取、判断等操作。
1. 安全获取列表元素(避免索引越界)
/**
* 安全获取列表元素,索引越界时返回默认值
*/
fun<T> List<T>.getOrNullSafe(index: Int, defaultValue: T): T {
returnif (index inthis.indices) this[index] else defaultValue
}
// 调用示例
funmain() {
val list = listOf(1, 2, 3)
println(list.getOrNullSafe(2, 0)) // 输出:3(正常索引)
println(list.getOrNullSafe(5, 0)) // 输出:0(索引越界)
}
2. 提取列表中对象的某个属性(转新列表)
// 先定义一个测试数据类
dataclassUser(val id: Long, val name: String, val age: Int)
/**
* 提取 List<User> 中的所有用户ID,返回 List<Long>
*/
fun List<User>.extractIds(): List<Long> {
returnthis.map { it.id }
}
/**
* 提取 List<User> 中的成年用户,返回 List<User>
*/
fun List<User>.filterAdults(): List<User> {
returnthis.filter { it.age >= 18 }
}
// 调用示例
funmain() {
val userList = listOf(
User(1, "张三", 20),
User(2, "李四", 17),
User(3, "王五", 25)
)
println(userList.extractIds()) // 输出:[1, 2, 3]
println(userList.filterAdults()) // 输出:[User(id=1, name=张三, age=20), User(id=3, name=王五, age=25)]
}
3. 判断列表是否为空或null(可空列表)
/**
* 判断可空列表是否为空(包括列表本身为null)
*/
fun<T> List<T>?.isNullOrEmptySafe(): Boolean {
returnthis == null || this.isEmpty()
}
/**
* 判断可空列表是否非空(包括列表本身不为null)
*/
fun<T> List<T>?.isNotEmptySafe(): Boolean {
return !this.isNullOrEmptySafe()
}
// 调用示例
funmain() {
val list1: List<Int>? = null
val list2 = emptyList<Int>()
val list3 = listOf(1, 2, 3)
println(list1.isNullOrEmptySafe()) // 输出:true
println(list2.isNullOrEmptySafe()) // 输出:true
println(list3.isNotEmptySafe()) // 输出:true
}
4. 列表去重(根据对象属性)
/**
* 根据对象的某个属性去重(保留第一个出现的元素)
* @param keySelector 提取去重依据的属性
*/
fun<T, K> List<T>.distinctByProperty(keySelector: (T) -> K): List<T> {
val seenKeys = mutableSetOf<K>()
returnthis.filter { seenKeys.add(keySelector(it)) }
}
// 调用示例
funmain() {
val userList = listOf(
User(1, "张三", 20),
User(2, "张三", 25), // 同名,需要去重
User(3, "李四", 17)
)
// 按姓名去重
val distinctList = userList.distinctByProperty { it.name }
println(distinctList) // 输出:[User(id=1, name=张三, age=20), User(id=3, name=李四, age=17)]
}
三、 可空类型(通用)扩展
可空类型判空是 Kotlin 开发的基础,这些扩展能简化空安全处理,让代码更简洁。
1. 安全执行代码块(非空时才执行)
/**
* 可空对象非空时,执行指定代码块
*/
fun<T> T?.letIfNotNull(block: (T) -> Unit) {
if (this != null) block(this)
}
// 调用示例
funmain() {
val str: String? = "Hello"
val nullStr: String? = null
str.letIfNotNull { println(it.length) } // 输出:5(非空,执行代码块)
nullStr.letIfNotNull { println(it.length) } // 无输出(为空,不执行)
}
2. 可空布尔值安全判断(默认返回false)
/**
* 可空布尔值安全判断,null 时返回 false
*/
funBoolean?.safeValue(): Boolean {
returnthis ?: false
}
// 调用示例
funmain() {
val bool1: Boolean? = true
val bool2: Boolean? = null
println(bool1.safeValue()) // 输出:true
println(bool2.safeValue()) // 输出:false
}
四、 自定义类扩展(业务场景适配)
针对项目中的自定义业务类写扩展,能大幅简化业务逻辑代码,提升可读性。
1. 用户类(User)扩展
dataclassUser(val id: Long, val name: String, val age: Int, val email: String?)
/**
* 判断用户是否成年
*/
val User.isAdult: Boolean
get() = this.age >= 18
/**
* 获取用户的有效邮箱,无邮箱时返回"未填写"
*/
fun User.getValidEmail(): String {
returnthis.email.orDefault("未填写")
}
/**
* 用户信息格式化输出
*/
fun User.formatInfo(): String {
return"用户ID:${this.id},姓名:${this.name},年龄:${this.age},邮箱:${this.getValidEmail()}"
}
// 调用示例
funmain() {
val user = User(1, "张三", 20, null)
println(user.isAdult) // 输出:true
println(user.getValidEmail()) // 输出:未填写
println(user.formatInfo()) // 输出:用户ID:1,姓名:张三,年龄:20,邮箱:未填写
}
2. 订单类(Order)扩展
dataclassOrder(
val orderId: String,
val amount: Double,
val status: Int, // 0:未支付,1:已支付,2:已取消
val createTime: String
)
/**
* 订单状态描述(转中文)
*/
fun Order.getStatusDesc(): String {
returnwhen (this.status) {
0 -> "未支付"
1 -> "已支付"
2 -> "已取消"
else -> "未知状态"
}
}
/**
* 判断订单是否有效(已支付且金额大于0)
*/
fun Order.isValid(): Boolean {
returnthis.status == 1 && this.amount > 0
}
// 调用示例
funmain() {
val order = Order("OD20260201", 99.9, 1, "2026-02-01 10:00:00")
println(order.getStatusDesc()) // 输出:已支付
println(order.isValid()) // 输出:true
}
五、 伴生对象扩展(类级工具)
针对有伴生对象的类写扩展,提供类级别的工具函数,无需创建实例即可调用。
1. 日志工具类扩展
classAppLogger{
// 定义伴生对象(无需写任何内容,仅作为扩展载体)
companionobject
}
/**
* 打印调试日志
*/
fun AppLogger.Companion.d(tag: String, message: String) {
println("[DEBUG] [$tag] $message")
}
/**
* 打印错误日志
*/
fun AppLogger.Companion.e(tag: String, message: String, throwable: Throwable? = null) {
val errorMsg = if (throwable != null) "$message\n${throwable.message}"else message
println("[ERROR] [$tag] $errorMsg")
}
// 调用示例
funmain() {
AppLogger.d("Main", "程序启动成功")
AppLogger.e("Main", "数据解析失败", IllegalArgumentException("格式错误"))
}
2. 用户工具类扩展
classUserHelper{
companionobject
}
/**
* 创建默认匿名用户
*/
fun UserHelper.Companion.createAnonymousUser(): User {
return User(0, "匿名用户", 0, null)
}
/**
* 批量创建测试用户
*/
fun UserHelper.Companion.createTestUsers(count: Int): List<User> {
return List(count) { index ->
User(index.toLong() + 1, "测试用户$index", 18 + index, "test$index@example.com")
}
}
// 调用示例
funmain() {
val anonymousUser = UserHelper.createAnonymousUser()
val testUsers = UserHelper.createTestUsers(3)
println(anonymousUser)
println(testUsers)
}
六、 新手使用建议
-
统一管理扩展:建议在项目中创建 extensions包,按类型分类存放扩展文件(如StringExtensions.kt、ListExtensions.kt),方便后续维护; -
避免过度扩展:只给类添加和其强相关的功能,不要把不相关的逻辑封装成扩展(比如给 String加订单处理逻辑); -
优先使用扩展函数:扩展属性因无后备字段,使用场景有限,日常开发中扩展函数更实用; -
注重兼容性:扩展函数不能访问原类的私有/内部成员,封装时仅依赖公开API,避免后续原类修改导致扩展失效。
七、 总结
-
这份示例覆盖了字符串、集合、自定义类三大核心场景,可直接复用至实际项目; -
扩展的核心价值是「不修改原类,简化调用,避免工具类泛滥」; -
合理使用扩展能让代码更优雅、可读性更强,是 Kotlin 开发的必备技巧。
你可以根据自己的项目业务,基于这些示例扩展出更多贴合实际需求的功能。
夜雨聆风
