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

53 阅读7分钟

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

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

为什么扩展函数至关重要

扩展函数允许你:

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

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

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

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

基础校验扩展

// ✓ 简洁的校验扩展函数
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() }
}

高级字符串扩展

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 操作符(?:)。这是处理空值时最广泛使用的模式之一。

空安全辅助函数

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

集合空安全扩展

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)

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

实用集合扩展

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

高级集合操作

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 扩展函数

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)

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

日期扩展函数

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 扩展

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 操作代码。

视图显示状态扩展

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)

视图交互扩展

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)

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

资源访问扩展函数

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)

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()) {
    // 执行逻辑
}

范围与边界扩展

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 扩展

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 扩展函数

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 扩展函数

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 扩展函数

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。从这十种模式开始尝试,你将见证你的代码库变得更加易于维护。