让你的代码更整洁:10 个必知的 Kotlin 扩展函数

扩展函数是 Kotlin 最强大的特性之一,但许多开发者并未充分发挥其潜力。在审阅了数万行 Kotlin 代码后,我发现:恰到好处地使用扩展函数,能将冗长、重复的代码转化为优雅且易读的表达式。

别再写那些工具类(Helper classes)和静态方法(Utility methods)了。这十个扩展函数模式将让你的 Kotlin 代码更整洁、更具表现力,且更易于维护。

为什么扩展函数至关重要

扩展函数允许你:

  • 无需继承即可扩展功能:直接为现有类添加新功能。
  • 提升可读性:通过领域特定语言(DSL)使代码更易理解。
  • 减少样板代码:彻底消除臃肿的工具类(Utility classes)。
  • 实现自然链式操作:让代码调用逻辑更顺畅。

让我们通过以下十个模式,看看它们是如何重塑你的代码库的。

模式 1:字符串校验扩展 (String Validation Extensions)

问题: 字符串校验逻辑散落在代码库的各个角落。

基础校验扩展

kotlin 复制代码
// ✓ 简洁的校验扩展函数
fun String?.isValidEmail(): Boolean {
    if (this == null) return false
    return matches(Regex("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$"))
}

fun String?.isValidPhone(): Boolean {
    if (this == null) return false
    return matches(Regex("^\\+?[1-9]\\d{1,14}$"))
}

fun String?.isValidUrl(): Boolean {
    if (this == null) return false
    return try {
        java.net.URL(this)
        true
    } catch (e: Exception) {
        false
    }
}

fun String.isAlphanumeric(): Boolean {
    return matches(Regex("^[a-zA-Z0-9]+$"))
}

fun String.containsDigit(): Boolean {
    return any { it.isDigit() }
}

高级字符串扩展

kotlin 复制代码
fun String.truncate(maxLength: Int, ellipsis: String = "..."): String {
    return if (length <= maxLength) this
    else take(maxLength - ellipsis.length) + ellipsis
}

fun String.capitalizeWords(): String {
    return split(" ").joinToString(" ") { word ->
        word.replaceFirstChar { if (it.isLowerCase()) it.titlecase() else it.toString() }
    }
}

fun String.removeWhitespace(): String {
    return replace("\s+".toRegex(), "")
}

fun String.toSlug(): String {
    return lowercase()
        .replace("\s+".toRegex(), "-")
        .replace("[^a-z0-9-]".toRegex(), "")
        .replace("-+".toRegex(), "-")
        .trim('-')
}

// 用法示例
val title = "This is a Long Article Title That Needs Truncation"
val short = title.truncate(20) // 结果: "This is a Long Ar..."

val name = "john doe"
val formatted = name.capitalizeWords() // 结果: "John Doe"

val slug = "My Blog Post!".toSlug() // 结果: "my-blog-post"

模式 2:空安全扩展 (Null Safety Extensions)

问题: 代码中充斥着重复的判空检查和 Elvis 操作符(?:)。这是处理空值时最广泛使用的模式之一。

空安全辅助函数

kotlin 复制代码
fun <T> T?.orDefault(default: T): T {
    return this ?: default
}

fun <T> T?.orThrow(exception: () -> Exception): T {
    return this ?: throw exception()
}

fun <T> T?.ifNull(action: () -> Unit): T? {
    if (this == null) action()
    return this
}

fun <T> T?.ifNotNull(action: (T) -> Unit): T? {
    if (this != null) action(this)
    return this
}

// 用法示例
val username: String? = null
val display = username.orDefault("Guest") // 结果: "Guest"

val userId: String? = null
val id = userId.orThrow { IllegalArgumentException("需要用户 ID") }

var errorShown = false
val result: String? = null
result.ifNull { errorShown = true }

val user: User? = getUser()
user.ifNotNull { println("找到用户: ${it.name}") }

集合空安全扩展

kotlin 复制代码
fun <T> List<T>?.orEmpty(): List<T> = this ?: emptyList()

fun <K, V> Map<K, V>?.orEmpty(): Map<K, V> = this ?: emptyMap()

fun <T> List<T>?.isNullOrEmpty(): Boolean {
    return this == null || isEmpty()
}

fun <T> List<T>?.isNotNullOrEmpty(): Boolean {
    return !isNullOrEmpty()
}

// 用法示例
val items: List<String>? = null
items.orEmpty().forEach { println(it) } // 安全遍历

val map: Map<String, Int>? = null
val count = map.orEmpty().size // 结果: 0

if (items.isNotNullOrEmpty()) {
    // 处理 items
}

模式 3:集合扩展 (Collection Extensions)

问题: 常见的集合操作往往需要编写冗长的代码。

实用集合扩展

kotlin 复制代码
fun <T> List<T>.second(): T {
    if (size < 2) throw NoSuchElementException("列表元素不足 2 个")
    return this[1]
}

fun <T> List<T>.secondOrNull(): T? {
    return if (size >= 2) this[1] else null
}

fun <T> List<T>.takeIfNotEmpty(): List<T>? {
    return if (isNotEmpty()) this else null
}

fun <T> List<T>.split(predicate: (T) -> Boolean): Pair<List<T>, List<T>> {
    return partition(predicate)
}

fun <T> Iterable<T>.sumByLong(selector: (T) -> Long): Long {
    return fold(0L) { sum, element -> sum + selector(element) }
}

// 用法示例
val numbers = listOf(1, 2, 3, 4, 5)
val secondNum = numbers.secondOrNull() // 结果: 2

val empty = emptyList<String>()
empty.takeIfNotEmpty() // 结果: null

val (evens, odds) = numbers.split { it % 2 == 0 }
// evens = [2, 4], odds = [1, 3, 5]

val files = listOf(
    File("file1.txt", 100L),
    File("file2.txt", 200L)
)
val totalSize = files.sumByLong { it.size } // 结果: 300L

高级集合操作

kotlin 复制代码
fun <T> List<T>.replaceAll(oldValue: T, newValue: T): List<T> {
    return map { if (it == oldValue) newValue else it }
}

fun <T> List<T>.chunkedBy(predicate: (T) -> Boolean): List<List<T>> {
    val result = mutableListOf<List<T>>()
    var currentChunk = mutableListOf<T>()
    
    forEach { item ->
        if (predicate(item) && currentChunk.isNotEmpty()) {
            result.add(currentChunk)
            currentChunk = mutableListOf()
        }
        currentChunk.add(item)
    }
    
    if (currentChunk.isNotEmpty()) {
        result.add(currentChunk)
    }
    
    return result
}

fun <T> List<T>.duplicates(): List<T> {
    return groupingBy { it }
        .eachCount()
        .filter { it.value > 1 }
        .keys
        .toList()
}

// 用法示例
val items = listOf(1, 2, 1, 3, 2, 4)
val dups = items.duplicates() // 结果: [1, 2]

val words = listOf("hello", "world", "hello", "kotlin")
val unique = words.replaceAll("hello", "hi") // 结果: ["hi", "world", "hi", "kotlin"]

模式 4:Context 扩展 (Context Extensions)

问题: Android 中重复繁琐的 Context 相关操作。

Context 扩展函数

kotlin 复制代码
fun Context.toast(message: String, duration: Int = Toast.LENGTH_SHORT) {
    Toast.makeText(this, message, duration).show()
}

fun Context.showAlertDialog(
    title: String,
    message: String,
    positiveButton: String = "确定",
    onPositive: () -> Unit = {}
) {
    AlertDialog.Builder(this)
        .setTitle(title)
        .setMessage(message)
        .setPositiveButton(positiveButton) { _, _ -> onPositive() }
        .show()
}

fun Context.hideKeyboard(view: View) {
    val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    imm.hideSoftInputFromWindow(view.windowToken, 0)
}

fun Context.showKeyboard(view: View) {
    view.requestFocus()
    val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    imm.showSoftInput(view, InputMethodManager.SHOW_IMPLICIT)
}

fun Context.isNetworkAvailable(): Boolean {
    val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
    val activeNetwork = connectivityManager.activeNetworkInfo
    return activeNetwork?.isConnected == true
}

// 在 Activity/Fragment 中的用法示例
toast("操作成功")

showAlertDialog(
    title = "确认",
    message = "删除该项目?",
    positiveButton = "删除"
) {
    deleteItem()
}

if (isNetworkAvailable()) {
    loadData()
} else {
    toast("无网络连接")
}

hideKeyboard(editText)

模式 5:日期与时间扩展 (Date and Time Extensions)

问题: 复杂的日期格式化与时间计算逻辑。

日期扩展函数

kotlin 复制代码
fun Date.formatTo(pattern: String = "yyyy-MM-dd"): String {
    val formatter = SimpleDateFormat(pattern, Locale.getDefault())
    return formatter.format(this)
}

fun Date.toCalendar(): Calendar {
    return Calendar.getInstance().apply {
        time = this@toCalendar
    }
}

fun Date.addDays(days: Int): Date {
    return toCalendar().apply {
        add(Calendar.DAY_OF_MONTH, days)
    }.time
}

fun Date.addHours(hours: Int): Date {
    return toCalendar().apply {
        add(Calendar.HOUR_OF_DAY, hours)
    }.time
}

fun Date.isToday(): Boolean {
    val today = Calendar.getInstance()
    val dateCalendar = toCalendar()
    return today.get(Calendar.YEAR) == dateCalendar.get(Calendar.YEAR) &&
           today.get(Calendar.DAY_OF_YEAR) == dateCalendar.get(Calendar.DAY_OF_YEAR)
}

fun Date.isFuture(): Boolean {
    return time > System.currentTimeMillis()
}

fun Date.isPast(): Boolean {
    return time < System.currentTimeMillis()
}

// 用法示例
val now = Date()
val formatted = now.formatTo("MMM dd, yyyy") // 结果: "Jan 08, 2026"

val tomorrow = now.addDays(1)
val inThreeHours = now.addHours(3)

if (deadline.isFuture()) {
    println("还有时间!")
}

现代日期 API 扩展

kotlin 复制代码
fun LocalDateTime.formatTo(pattern: String = "yyyy-MM-dd HH:mm:ss"): String {
    return format(DateTimeFormatter.ofPattern(pattern))
}

fun LocalDate.isWeekend(): Boolean {
    return dayOfWeek == DayOfWeek.SATURDAY || dayOfWeek == DayOfWeek.SUNDAY
}
fun LocalDate.isWeekday(): Boolean {
    return !isWeekend()
}
fun LocalDateTime.toEpochMillis(): Long {
    return atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
}
fun Long.toLocalDateTime(): LocalDateTime {
    return LocalDateTime.ofInstant(Instant.ofEpochMilli(this), ZoneId.systemDefault())
}
// Usage
val now = LocalDateTime.now()
val display = now.formatTo("MMM dd, yyyy HH:mm") // "Jan 08, 2026 15:30"
val date = LocalDate.now()
if (date.isWeekend()) {
    println("It's the weekend!")
}
val timestamp = System.currentTimeMillis()
val dateTime = timestamp.toLocalDateTime()

模式 6:视图扩展 (View Extensions)

问题: 重复的 View 操作代码。

视图显示状态扩展

kotlin 复制代码
fun View.visible() {
    visibility = View.VISIBLE
}

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

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

fun View.toggleVisibility() {
    visibility = if (visibility == View.VISIBLE) View.GONE else View.VISIBLE
}

fun View.isVisible(): Boolean = visibility == View.VISIBLE

fun View.isGone(): Boolean = visibility == View.GONE

fun View.visibleIf(condition: Boolean) {
    visibility = if (condition) View.VISIBLE else View.GONE
}

fun View.goneIf(condition: Boolean) {
    visibility = if (condition) View.GONE else View.VISIBLE
}

// 用法示例
loadingSpinner.visible()
errorMessage.gone()
submitButton.visibleIf(form.isValid())
errorView.goneIf(data != null)

视图交互扩展

kotlin 复制代码
fun View.onClick(action: () -> Unit) {
    setOnClickListener { action() }
}

fun View.onLongClick(action: () -> Boolean) {
    setOnLongClickListener { action() }
}

fun View.setMargin(left: Int = 0, top: Int = 0, right: Int = 0, bottom: Int = 0) {
    val params = layoutParams as? ViewGroup.MarginLayoutParams
    params?.setMargins(left, top, right, bottom)
    layoutParams = params
}

fun View.setPadding(padding: Int) {
    setPadding(padding, padding, padding, padding)
}

fun View.updatePadding(
    left: Int = paddingLeft,
    top: Int = paddingTop,
    right: Int = paddingRight,
    bottom: Int = paddingBottom
) {
    setPadding(left, top, right, bottom)
}

// 用法示例
button.onClick {
    performAction()
}

imageView.onLongClick {
    showContextMenu()
    true
}

// 仅设置左右边距,无需关心其他方向
view.setMargin(left = 16, right = 16)

// 一键设置全方向内边距
view.setPadding(24)

模式 7:资源访问扩展 (Resource Extensions)

问题: 资源访问代码过于冗长。

资源访问扩展函数

kotlin 复制代码
fun Context.drawable(@DrawableRes id: Int): Drawable? {
    return ContextCompat.getDrawable(this, id)
}

fun Context.color(@ColorRes id: Int): Int {
    return ContextCompat.getColor(this, id)
}

fun Context.string(@StringRes id: Int, vararg args: Any): String {
    return getString(id, *args)
}

fun Context.dimension(@DimenRes id: Int): Float {
    return resources.getDimension(id)
}

fun Context.dimensionPixelSize(@DimenRes id: Int): Int {
    return resources.getDimensionPixelSize(id)
}

fun View.drawable(@DrawableRes id: Int): Drawable? {
    return context.drawable(id)
}

fun View.color(@ColorRes id: Int): Int {
    return context.color(id)
}

fun View.string(@StringRes id: Int, vararg args: Any): String {
    return context.string(id, *args)
}

// 用法示例
val icon = drawable(R.drawable.ic_star)
val primaryColor = color(R.color.primary)
val message = string(R.string.welcome_message, userName)
val spacing = dimensionPixelSize(R.dimen.spacing_medium)

// 在 View 中使用
imageView.setImageDrawable(drawable(R.drawable.ic_profile))
textView.setTextColor(color(R.color.text_primary))

模式 8:数值扩展 (Number Extensions)

kotlin 复制代码
fun Int.formatWithCommas(): String {
    return String.format("%,d", this)
}

fun Double.formatAsPrice(currencySymbol: String = "$"): String {
    return "$currencySymbol%.2f".format(this)
}

fun Float.roundTo(decimals: Int): Float {
    val multiplier = 10.0.pow(decimals)
    return (this * multiplier).roundToInt() / multiplier.toFloat()
}

fun Int.toBoolean(): Boolean = this != 0

fun Boolean.toInt(): Int = if (this) 1 else 0

fun Int.isEven(): Boolean = this % 2 == 0

fun Int.isOdd(): Boolean = !isEven()

// 用法示例
val number = 1234567
val formatted = number.formatWithCommas() // 结果: "1,234,567"

val price = 49.99
val display = price.formatAsPrice() // 结果: "$49.99"

val value = 3.14159f
val rounded = value.roundTo(2) // 结果: 3.14f

val flag = 1
if (flag.toBoolean()) {
    // 执行逻辑
}

范围与边界扩展

kotlin 复制代码
fun Int.clamp(min: Int, max: Int): Int {
    return when {
        this < min -> min
        this > max -> max
        else -> this
    }
}

fun Float.clamp(min: Float, max: Float): Float {
    return when {
        this < min -> min
        this > max -> max
        else -> this
    }
}

fun Int.inRange(range: IntRange): Boolean {
    return this in range
}

fun Int.toPercentage(total: Int): Float {
    return if (total == 0) 0f else (this.toFloat() / total) * 100
}

// 用法示例
val value = 150
val clamped = value.clamp(0, 100) // 结果: 100

val progress = 75
if (progress.inRange(50..100)) {
    println("进度过半")
}

val completed = 30
val total = 100
val percentage = completed.toPercentage(total) // 结果: 30.0

模式 9:Flow 与 LiveData 扩展 (Flow and LiveData Extensions)

问题: Flow 和 LiveData 的操作过于繁琐。

Flow 扩展

kotlin 复制代码
fun <T> Flow<T>.throttleFirst(windowDuration: Long): Flow<T> = flow {
    var lastEmissionTime = 0L
    collect { value ->
        val currentTime = System.currentTimeMillis()
        if (currentTime - lastEmissionTime >= windowDuration) {
            lastEmissionTime = currentTime
            emit(value)
        }
    }
}

fun <T> Flow<List<T>>.filterNotEmpty(): Flow<List<T>> {
    return filter { it.isNotEmpty() }
}

fun <T> Flow<T?>.filterNotNull(): Flow<T> {
    return mapNotNull { it }
}

fun <T> Flow<T>.onEachDebounce(timeoutMillis: Long, action: suspend (T) -> Unit): Flow<T> {
    return debounce(timeoutMillis).onEach { action(it) }
}

// 用法示例
searchQueryFlow
    .debounce(300) // 防抖
    .filterNotNull()
    .collect { query ->
        performSearch(query)
    }

clickFlow
    .throttleFirst(1000) // 节流,防止连续点击
    .collect {
        handleClick()
    }

itemsFlow
    .filterNotEmpty()
    .collect { items ->
        displayItems(items)
    }

LiveData 扩展函数

kotlin 复制代码
fun <T> LiveData<T>.observeOnce(owner: LifecycleOwner, observer: (T) -> Unit) {
    observe(owner, object : Observer<T> {
        override fun onChanged(value: T) {
            observer(value)
            removeObserver(this)
        }
    })
}

fun <T> LiveData<T?>.filterNotNull(): LiveData<T> {
    return map { it }.filter { it != null } as LiveData<T>
}

fun <X, Y> LiveData<X>.map(transform: (X) -> Y): LiveData<Y> {
    return Transformations.map(this, transform)
}

fun <X, Y> LiveData<X>.switchMap(transform: (X) -> LiveData<Y>): LiveData<Y> {
    return Transformations.switchMap(this, transform)
}

// 用法示例
userLiveData.observeOnce(viewLifecycleOwner) { user ->
    initializeUser(user) // 仅观察一次
}

val userNameLiveData = userLiveData
    .filterNotNull()
    .map { it.name }

模式 10:Intent 与 Bundle 扩展 (Intent and Bundle Extensions)

问题: Intent 的创建和 Bundle 的操作代码过于繁琐。

Intent 扩展函数

kotlin 复制代码
inline fun <reified T : Activity> Context.startActivity(
    options: Bundle? = null,
    init: Intent.() -> Unit = {}
) {
    val intent = Intent(this, T::class.java)
    intent.init()
    startActivity(intent, options)
}

inline fun <reified T : Activity> Context.startActivityWithExtras(
    vararg params: Pair<String, Any?>
) {
    val intent = Intent(this, T::class.java)
    params.forEach { (key, value) ->
        when (value) {
            is String -> intent.putExtra(key, value)
            is Int -> intent.putExtra(key, value)
            is Boolean -> intent.putExtra(key, value)
            is Parcelable -> intent.putExtra(key, value)
            is Serializable -> intent.putExtra(key, value)
        }
    }
    startActivity(intent)
}

fun Intent.clearStack() {
    flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}

// 用法示例
startActivity<ProfileActivity> {
    putExtra("userId", userId)
    putExtra("userName", userName)
}

startActivityWithExtras<DetailActivity>(
    "itemId" to itemId,
    "showComments" to true
)

val intent = Intent(this, MainActivity::class.java)
intent.clearStack()
startActivity(intent)

Bundle 扩展函数

kotlin 复制代码
inline fun <reified T : Parcelable> Bundle.getParcelableCompat(key: String): T? {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        getParcelable(key, T::class.java)
    } else {
        @Suppress("DEPRECATION")
        getParcelable(key)
    }
}

inline fun <reified T : Serializable> Bundle.getSerializableCompat(key: String): T? {
    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
        getSerializable(key, T::class.java)
    } else {
        @Suppress("DEPRECATION")
        getSerializable(key) as? T
    }
}

fun bundleOf(vararg pairs: Pair<String, Any?>): Bundle {
    return Bundle().apply {
        pairs.forEach { (key, value) ->
            when (value) {
                is String -> putString(key, value)
                is Int -> putInt(key, value)
                is Boolean -> putBoolean(key, value)
                is Parcelable -> putParcelable(key, value)
                is Serializable -> putSerializable(key, value)
            }
        }
    }
}

// 用法示例
val bundle = bundleOf(
    "userId" to 123,
    "userName" to "John",
    "isPremium" to true
)

val user: User? = arguments?.getParcelableCompat("user")
val data: MyData? = savedInstanceState?.getSerializableCompat("data")

扩展函数将重复的代码转化为简洁且具表现力的 API。从这十种模式开始尝试,你将见证你的代码库变得更加易于维护。

相关推荐
城东米粉儿2 小时前
Android VSync 笔记
android
城东米粉儿2 小时前
Android SurfaceFlinger 笔记
android
似霰2 小时前
Android 日志系统5——logd 写日志过程分析二
android·log
hewence12 小时前
Kotlin CoroutineContext 详解
android·开发语言·kotlin
Albert Edison3 小时前
【Python】文件
android·服务器·python
大模型玩家七七3 小时前
效果评估:如何判断一个祝福 AI 是否“走心”
android·java·开发语言·网络·人工智能·batch
Aurora4193 小时前
Android事件分发逻辑--针对事件分发相关函数的讲解
android
似霰4 小时前
Android 日志系统4——logd 写日志过程分析一
android
youyoulg4 小时前
利用Android Studio编译Android上可直接执行的二进制
android·ide·android studio