乐于分享
好东西不私藏

Kotlin 扩展:不修改源码,给类新增功能(新手易懂版)

Kotlin 扩展:不修改源码,给类新增功能(新手易懂版)

Kotlin 的扩展功能是个超实用的特性,它能让你在不修改原类代码、不使用继承的前提下,给任意类(包括系统类、第三方库的类)新增函数和属性。扩展后的功能调用起来和类的原生成员一模一样。

一、核心概念:什么是扩展?

扩展的本质是编译器的语法糖——它并没有真正修改原类,只是在调用时,编译器帮你把扩展函数/属性转成了普通的函数调用。

举个简单例子:你想给 String 类加一个“截断过长字符串”的功能,不用改 String 的源码,直接写一个扩展函数就行。

扩展主要分两种:

  1. 扩展函数:给类新增方法
  2. 扩展属性:给类新增属性

二、基础用法1:扩展函数

扩展函数是最常用的扩展类型,语法很简单:**fun 接收者类型.函数名(参数): 返回值 { 实现 }**。

  • 接收者类型:你要扩展的类(比如 StringList
  • 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(12345)
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() // 报错
}

七、新手避坑要点

  1. 扩展不会真正修改原类:扩展只是编译器的语法糖,原类的字节码没有任何变化。
  2. 扩展属性没有后备字段:必须手动写 get/set 方法,不能用 field 关键字。
  3. 扩展不能访问原类的私有成员:扩展函数只能访问原类的 public 成员。
  4. 避免滥用扩展:不要给一个类加太多不相关的扩展,否则会让代码难以维护。

八、扩展的适用场景

  1. 给系统类加功能:比如给 StringList 加业务相关的工具函数。
  2. 优化第三方库的 API:第三方库的类不能修改,用扩展包装成更易用的接口。
  3. 避免工具类泛滥:不用写 StringUtilsListUtils 这种工具类,直接给对应类加扩展。

扩展是 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 != 11returnthis// 非11位直接返回原字符串
returnthis.replaceRange(37"****")
}

// 调用示例
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(123)
    println(list.getOrNullSafe(20)) // 输出:3(正常索引)
    println(list.getOrNullSafe(50)) // 输出:0(索引越界)
}

2. 提取列表中对象的某个属性(转新列表)

// 先定义一个测试数据类
dataclassUser(val id: Longval 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(123)

    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: Longval name: String, val age: Intval 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"张三"20null)
    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.91"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"匿名用户"0null)
}

/**
 * 批量创建测试用户
 */

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)
}

六、 新手使用建议

  1. 统一管理扩展:建议在项目中创建 extensions 包,按类型分类存放扩展文件(如 StringExtensions.ktListExtensions.kt),方便后续维护;
  2. 避免过度扩展:只给类添加和其强相关的功能,不要把不相关的逻辑封装成扩展(比如给 String 加订单处理逻辑);
  3. 优先使用扩展函数:扩展属性因无后备字段,使用场景有限,日常开发中扩展函数更实用;
  4. 注重兼容性:扩展函数不能访问原类的私有/内部成员,封装时仅依赖公开API,避免后续原类修改导致扩展失效。

七、 总结

  1. 这份示例覆盖了字符串、集合、自定义类三大核心场景,可直接复用至实际项目;
  2. 扩展的核心价值是「不修改原类,简化调用,避免工具类泛滥」;
  3. 合理使用扩展能让代码更优雅、可读性更强,是 Kotlin 开发的必备技巧。

你可以根据自己的项目业务,基于这些示例扩展出更多贴合实际需求的功能。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » Kotlin 扩展:不修改源码,给类新增功能(新手易懂版)

评论 抢沙发

9 + 6 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮