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
}
总结
创建扩展文件的最佳实践:
- 合理组织:按类型或功能组织扩展文件
- 明确命名:使用清晰的包名和文件名
- 保持简洁:每个扩展文件应该有明确的职责
- 充分文档:为扩展提供完整的 KDoc 文档
- 全面测试:为扩展编写单元测试
- 性能考虑:注意扩展的性能影响
- 避免冲突:使用明确的包结构和导入别名
- 适度使用:不要过度使用扩展,只在确实需要时创建
通过良好的扩展文件组织,可以创建可维护、可测试和可重用的代码库。