2-2-21 快速掌握Kotlin-定义扩展文件

Kotlin 语言中定义扩展文件

扩展文件是组织和管理扩展函数和扩展属性的最佳方式。通过将相关的扩展放在专门的文件中,可以提高代码的可维护性和可读性。

1. 扩展文件的基本结构

创建扩展文件

通常以 类名 + Extensions.kt 的形式命名:

bash 复制代码
项目结构:
├── src/main/kotlin
│   ├── com/example/
│   │   ├── StringExtensions.kt
│   │   ├── CollectionExtensions.kt
│   │   ├── DateExtensions.kt
│   │   └── ViewExtensions.kt

基本示例

kotlin 复制代码
// StringExtensions.kt
package com.example.extensions

// 扩展函数
fun String.removeWhitespace(): String {
    return this.replace("\\s".toRegex(), "")
}

fun String.isEmail(): Boolean {
    val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\$"
    return this.matches(emailRegex.toRegex())
}

// 扩展属性
val String.wordCount: Int
    get() = this.split("\\s+".toRegex()).count { it.isNotBlank() }

val String.initials: String
    get() = this.split(" ")
        .filter { it.isNotEmpty() }
        .map { it.first().uppercaseChar() }
        .joinToString("")

2. 按功能组织的扩展文件

按领域组织

kotlin 复制代码
// ValidationExtensions.kt - 验证相关的扩展
package com.example.extensions.validation

fun String.isValidEmail(): Boolean {
    return this.matches(Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\$"))
}

fun String.isValidPhone(): Boolean {
    return this.matches(Regex("^\\+?[0-9]{10,15}\$"))
}

fun String.isValidPassword(): Boolean {
    val hasUpper = Regex("[A-Z]").containsMatchIn(this)
    val hasLower = Regex("[a-z]").containsMatchIn(this)
    val hasDigit = Regex("[0-9]").containsMatchIn(this)
    val hasSpecial = Regex("[!@#\$%^&*()_+\\-=\\[\\]{};':\"\\\\|,.<>/?]").containsMatchIn(this)
    return length >= 8 && hasUpper && hasLower && hasDigit && hasSpecial
}

// DataFormatExtensions.kt - 数据格式化相关的扩展
package com.example.extensions.format

import java.text.SimpleDateFormat
import java.util.*

fun Date.toFormattedString(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
    return SimpleDateFormat(pattern).format(this)
}

fun Long.toFormattedFileSize(): String {
    val units = listOf("B", "KB", "MB", "GB", "TB")
    var size = this.toDouble()
    var unitIndex = 0
    
    while (size >= 1024 && unitIndex < units.size - 1) {
        size /= 1024
        unitIndex++
    }
    
    return "%.2f %s".format(size, units[unitIndex])
}

按框架/平台组织

kotlin 复制代码
// AndroidExtensions.kt
package com.example.extensions.android

import android.view.View
import android.widget.TextView
import androidx.core.content.ContextCompat

// View 扩展
fun View.show() {
    visibility = View.VISIBLE
}

fun View.hide() {
    visibility = View.GONE
}

fun View.invisible() {
    visibility = View.INVISIBLE
}

fun View.setVisible(isVisible: Boolean) {
    visibility = if (isVisible) View.VISIBLE else View.GONE
}

// TextView 扩展
fun TextView.setTextColorRes(colorRes: Int) {
    setTextColor(ContextCompat.getColor(context, colorRes))
}

// CoroutineExtensions.kt
package com.example.extensions.coroutines

import kotlinx.coroutines.*

fun <T> debounce(
    waitMillis: Long = 300L,
    coroutineScope: CoroutineScope,
    destinationFunction: (T) -> Unit
): (T) -> Unit {
    var debounceJob: Job? = null
    return { param: T ->
        debounceJob?.cancel()
        debounceJob = coroutineScope.launch {
            delay(waitMillis)
            destinationFunction(param)
        }
    }
}

3. 使用顶层函数和属性

顶层扩展

kotlin 复制代码
// StringExtensions.kt
// 顶层扩展,可以在任何地方导入使用

// 全局可用的 String 扩展
fun String.toTitleCase(): String {
    return this.split(" ").joinToString(" ") { word ->
        word.replaceFirstChar { it.uppercase() }
    }
}

// 带有接收者的顶层函数
fun withLogging(tag: String, block: () -> Unit) {
    println("[$tag] 开始执行")
    val startTime = System.currentTimeMillis()
    block()
    val endTime = System.currentTimeMillis()
    println("[$tag] 执行完成,耗时 ${endTime - startTime}ms")
}

// 顶层属性
val String.Companion.EMPTY: String
    get() = ""

// 使用伴生对象扩展
fun String.Companion.random(length: Int = 10): String {
    val charset = ('a'..'z') + ('A'..'Z') + ('0'..'9')
    return (1..length)
        .map { charset.random() }
        .joinToString("")
}

4. 扩展文件的最佳实践

保持单一职责

kotlin 复制代码
// 不好的做法:混合不同类型的扩展
// MixedExtensions.kt ❌
package com.example

// String 扩展
fun String.method1() { ... }
// Date 扩展  
fun Date.method2() { ... }
// List 扩展
fun <T> List<T>.method3() { ... }

// 好的做法:按类型分离 ✅
// StringExtensions.kt
package com.example.extensions.string

fun String.method1() { ... }

// DateExtensions.kt
package com.example.extensions.date

fun Date.method2() { ... }

// ListExtensions.kt
package com.example.extensions.collection

fun <T> List<T>.method3() { ... }

使用明确的包名

kotlin 复制代码
// 清晰的包结构
package com.example.project.extensions.string
// 而不是
package com.example.project  // 容易与主代码混淆

// 按功能分组的包
package com.example.extensions.validation
package com.example.extensions.format
package com.example.extensions.ui
package com.example.extensions.network

5. 使用 import 优化

使用 import 别名

kotlin 复制代码
// 避免命名冲突
import com.example.extensions.string.toTitleCase as toCustomTitleCase
import com.otherlibrary.extensions.string.toTitleCase as toOtherTitleCase

fun test() {
    val text = "hello world"
    println(text.toCustomTitleCase())  // 使用自定义扩展
    println(text.toOtherTitleCase())   // 使用其他库的扩展
}

批量导入

kotlin 复制代码
// 创建扩展文件的索引
// Extensions.kt
@file:JvmName("Extensions")

package com.example.extensions

// 重新导出所有扩展
fun String.customMethod() = "custom"
fun Int.double() = this * 2

// 在其他文件中只需要导入这一个文件
// Main.kt
import com.example.extensions.*

fun main() {
    println("test".customMethod())
    println(5.double())
}

6. 创建扩展库

构建可重用的扩展模块

kotlin 复制代码
// build.gradle.kts
kotlin {
    sourceSets {
        val commonMain by getting {
            dependencies {
                // 依赖
            }
        }
        val androidMain by getting
        val iosMain by getting
    }
}

// 创建多平台扩展
// commonMain/kotlin/com/example/extensions/StringExtensions.kt
expect fun String.platformSpecificMethod(): String

// androidMain/kotlin/com/example/extensions/StringExtensions.kt
actual fun String.platformSpecificMethod(): String {
    return "Android: $this"
}

// iosMain/kotlin/com/example/extensions/StringExtensions.kt
actual fun String.platformSpecificMethod(): String {
    return "iOS: $this"
}

7. 扩展文件中的常量

在扩展文件中定义常量

kotlin 复制代码
// Constants.kt 或直接放在扩展文件中
package com.example.extensions

// 与扩展相关的常量
const val EMAIL_REGEX = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\$"
const val PHONE_REGEX = "^\\+?[0-9]{10,15}\$"
const val MAX_FILE_SIZE_BYTES = 10 * 1024 * 1024  // 10MB

// 使用常量的扩展函数
fun String.isValidEmail(): Boolean {
    return this.matches(Regex(EMAIL_REGEX))
}

8. 扩展文件中的工具类

创建支持扩展的工具类

kotlin 复制代码
// ExtensionUtils.kt
package com.example.extensions

import kotlin.reflect.KClass

// 为扩展提供工具函数
object ExtensionUtils {
    
    // 检查扩展是否存在
    fun hasExtension(value: Any, extensionName: String): Boolean {
        return try {
            value::class.members.any { it.name == extensionName }
            true
        } catch (e: Exception) {
            false
        }
    }
    
    // 动态调用扩展
    fun <T : Any> callExtension(
        receiver: Any,
        extensionClass: KClass<T>,
        methodName: String,
        vararg args: Any
    ): Any? {
        // 实现动态调用逻辑
        return null
    }
}

9. 测试扩展文件

为扩展编写单元测试

kotlin 复制代码
// StringExtensionsTest.kt
package com.example.extensions.test

import com.example.extensions.string.*
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue

class StringExtensionsTest {
    
    @Test
    fun testIsEmail() {
        assertTrue("test@example.com".isEmail())
        assertFalse("invalid-email".isEmail())
    }
    
    @Test
    fun testWordCount() {
        assertEquals(3, "Hello world test".wordCount)
        assertEquals(0, "".wordCount)
        assertEquals(1, "Single".wordCount)
    }
    
    @Test
    fun testToTitleCase() {
        assertEquals("Hello World", "hello world".toTitleCase())
        assertEquals("Test String", "test string".toTitleCase())
    }
}

测试配置

kotlin 复制代码
// build.gradle.kts 配置测试
dependencies {
    testImplementation("org.jetbrains.kotlin:kotlin-test")
    testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
}

sourceSets {
    test {
        kotlin.srcDirs("src/test/kotlin")
    }
}

10. 文档化扩展文件

为扩展添加 KDoc 文档

kotlin 复制代码
/**
 * 字符串相关的扩展函数和属性
 * 
 * 这个文件包含了用于字符串处理的扩展函数,
 * 包括验证、格式化、转换等功能。
 * 
 * @author Your Name
 * @since 1.0.0
 */
package com.example.extensions.string

/**
 * 检查字符串是否是有效的电子邮件地址
 * 
 * 这个方法使用正则表达式验证电子邮件格式,
 * 支持大多数常见的电子邮件格式。
 * 
 * @return 如果是有效的电子邮件地址返回 true,否则返回 false
 * 
 * @sample com.example.extensions.test.StringExtensionsTest.testIsEmail
 * 
 * @see [RFC 5322](https://tools.ietf.org/html/rfc5322)
 */
fun String.isEmail(): Boolean {
    val emailRegex = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\$"
    return this.matches(emailRegex.toRegex())
}

/**
 * 将字符串转换为标题格式(每个单词首字母大写)
 * 
 * 示例:
 * ```
 * "hello world".toTitleCase()  // 返回 "Hello World"
 * "test-string".toTitleCase()  // 返回 "Test-string"
 * ```
 * 
 * @receiver 要转换的字符串
 * @return 标题格式的字符串
 * 
 * @throws IllegalArgumentException 如果字符串为 null
 */
fun String.toTitleCase(): String {
    return this.split(" ").joinToString(" ") { word ->
        word.replaceFirstChar { it.uppercase() }
    }
}

11. 实际项目结构示例

完整的项目结构

bash 复制代码
my-project/
├── src/main/kotlin/
│   ├── com/example/
│   │   ├── extensions/
│   │   │   ├── string/
│   │   │   │   ├── StringValidators.kt
│   │   │   │   ├── StringFormatters.kt
│   │   │   │   └── StringConverters.kt
│   │   │   ├── collection/
│   │   │   │   ├── ListExtensions.kt
│   │   │   │   ├── MapExtensions.kt
│   │   │   │   └── SetExtensions.kt
│   │   │   ├── date/
│   │   │   │   ├── DateExtensions.kt
│   │   │   │   └── DateTimeExtensions.kt
│   │   │   ├── ui/
│   │   │   │   ├── ViewExtensions.kt
│   │   │   │   └── FragmentExtensions.kt
│   │   │   └── Extensions.kt  // 聚合所有扩展
│   │   ├── models/
│   │   ├── network/
│   │   └── utils/
│   └── resources/
├── src/test/kotlin/
│   └── com/example/extensions/
│       ├── string/
│       ├── collection/
│       └── date/
└── build.gradle.kts

聚合扩展文件

kotlin 复制代码
// Extensions.kt - 聚合文件
@file:JvmName("Extensions")

package com.example.extensions

// 重新导出所有扩展,方便一次性导入
@file:Suppress("unused")

// 字符串扩展
fun String.isValidEmail() = com.example.extensions.string.isEmail(this)
fun String.toTitleCase() = com.example.extensions.string.toTitleCase(this)

// 集合扩展
fun <T> List<T>.secondOrNull() = com.example.extensions.collection.secondOrNull(this)
fun <K, V> Map<K, V>.invert() = com.example.extensions.collection.invert(this)

// 日期扩展
fun java.util.Date.toFormattedString() = com.example.extensions.date.toFormattedString(this)

// 使用示例:
// import com.example.extensions.*  // 一次性导入所有扩展

12. 性能优化建议

避免重复计算

kotlin 复制代码
// 使用缓存或延迟初始化
val String.words: List<String> by lazy {
    this.split("\\s+".toRegex())
}

// 对于频繁使用的扩展,考虑性能
inline fun String.transformInline(transform: (String) -> String): String {
    return transform(this)
}

// 避免创建不必要的对象
fun String.getCharacterFrequency(): Map<Char, Int> {
    val frequency = mutableMapOf<Char, Int>()
    for (char in this) {
        frequency[char] = frequency.getOrDefault(char, 0) + 1
    }
    return frequency
}

13. 常见陷阱与解决方案

陷阱 1:扩展冲突

kotlin 复制代码
// 解决方案:使用明确的包名和导入别名
package com.example.project.extensions.string

fun String.customFormat(): String = "Custom: $this"

// 在其他文件中
import com.example.project.extensions.string.customFormat as myCustomFormat
import com.other.library.extensions.string.customFormat as otherCustomFormat

val result1 = "test".myCustomFormat()
val result2 = "test".otherCustomFormat()

陷阱 2:过度扩展

kotlin 复制代码
// 不好的做法:为所有类添加大量扩展
// 好的做法:只在确实需要时创建扩展

// 评估标准:
// 1. 这个功能是否是类的核心职责?
// 2. 使用频率是否足够高?
// 3. 是否有更好的替代方案(如工具函数)?

陷阱 3:忽略可空性

kotlin 复制代码
// 不好的做法:忽略可空性
fun String?.toUppercase(): String = this?.uppercase() ?: ""

// 好的做法:明确处理可空性
fun String?.safeToUppercase(default: String = ""): String {
    return this?.uppercase() ?: default
}

总结

创建扩展文件的最佳实践:

  1. 合理组织:按类型或功能组织扩展文件
  2. 明确命名:使用清晰的包名和文件名
  3. 保持简洁:每个扩展文件应该有明确的职责
  4. 充分文档:为扩展提供完整的 KDoc 文档
  5. 全面测试:为扩展编写单元测试
  6. 性能考虑:注意扩展的性能影响
  7. 避免冲突:使用明确的包结构和导入别名
  8. 适度使用:不要过度使用扩展,只在确实需要时创建

通过良好的扩展文件组织,可以创建可维护、可测试和可重用的代码库。

相关推荐
成都大菠萝4 小时前
2-2-18 快速掌握Kotlin-扩展属性
android
成都大菠萝4 小时前
2-2-19 快速掌握Kotlin-可空类型扩展函数
android
成都大菠萝4 小时前
2-2-23 快速掌握Kotlin-apply函数详解
android
2501_916007474 小时前
iOS 证书如何创建,从能生成到能长期使用
android·macos·ios·小程序·uni-app·cocoa·iphone
Just_Paranoid5 小时前
【AOSP】Android Dump 信息快速定位方法
android·adb·framework·service·aosp·dumpsys
帅得不敢出门5 小时前
MTK Android11获取真实wifi mac地址
android·mtk
成都大菠萝5 小时前
2-2-16 快速掌握Kotlin-泛型扩展函数
android
I'm Jie5 小时前
Gradle 多模块依赖集中管理方案,Version Catalogs 详解(Kotlin DSL)
android·java·spring boot·kotlin·gradle·maven
BoomHe5 小时前
Android 13 (API 33)开发自己的 Settings ,如何配置 WiFi BT 权限
android