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