coerceIn 是 Kotlin 标准库中的一个扩展函数,用于将一个值限制在指定的范围内。如果值在范围内,则返回该值;如果小于最小值,则返回最小值;如果大于最大值,则返回最大值。
基本语法
// 限制在最小值和最大值之间
fun <T : Comparable<T>> T.coerceIn(minimumValue: T, maximumValue: T): T
// 限制在一个范围内
fun <T : Comparable<T>> T.coerceIn(range: ClosedRange<T>): T
基本用法
1. 数值范围限制
fun main() {
// 基本整数限制
val x = 10
val clamped1 = x.coerceIn(1, 5) // 10 > 5,所以返回 5
println(clamped1) // 输出: 5
val clamped2 = x.coerceIn(20, 30) // 10 < 20,所以返回 20
println(clamped2) // 输出: 20
val clamped3 = x.coerceIn(8, 12) // 10 在范围内,返回 10
println(clamped3) // 输出: 10
// 浮点数限制
val pi = 3.14
val clampedPi = pi.coerceIn(0.0, 1.0) // 3.14 > 1.0,返回 1.0
println(clampedPi) // 输出: 1.0
// 字符限制
val char = 'g'
val clampedChar = char.coerceIn('a', 'f') // 'g' > 'f',返回 'f'
println(clampedChar) // 输出: f
}
2. 使用范围对象
fun main() {
val value = 15
// 使用范围对象
val range1 = 10..20
val result1 = value.coerceIn(range1)
println(result1) // 输出: 15
val range2 = 1..5
val result2 = value.coerceIn(range2)
println(result2) // 输出: 5
val range3 = 20..30
val result3 = value.coerceIn(range3)
println(result3) // 输出: 20
// 使用 until(半开区间,需要先转换为闭区间)
val halfOpen = 10 until 20 // 10..19
val closedRange = 10..19
println(value.coerceIn(closedRange)) // 输出: 15
}
实际应用场景
1. UI 开发中的边界检查
data class View(val x: Int, val y: Int, val width: Int, val height: Int) {
fun moveTo(newX: Int, newY: Int, screenWidth: Int, screenHeight: Int): View {
// 确保视图不会移出屏幕
val clampedX = newX.coerceIn(0, screenWidth - width)
val clampedY = newY.coerceIn(0, screenHeight - height)
return this.copy(x = clampedX, y = clampedY)
}
}
fun main() {
val screen = View(0, 0, 800, 600)
val dialog = View(0, 0, 200, 150)
// 尝试移动到屏幕外
val moved = dialog.moveTo(700, 500, screen.width, screen.height)
println("Dialog position: (${moved.x}, ${moved.y})")
// 输出: Dialog position: (600, 450)
// 因为 700 > (800-200=600),500 > (600-150=450)
}
2. 游戏开发
data class GameCharacter(
val name: String,
var health: Int,
var position: Position
) {
data class Position(var x: Int, var y: Int)
fun move(dx: Int, dy: Int, worldBounds: IntRange) {
position.x = (position.x + dx).coerceIn(worldBounds)
position.y = (position.y + dy).coerceIn(worldBounds)
}
fun takeDamage(damage: Int) {
health = (health - damage).coerceIn(0, 100)
}
fun heal(amount: Int) {
health = (health + amount).coerceIn(0, 100)
}
}
fun main() {
val player = GameCharacter("Hero", 80, GameCharacter.Position(5, 5))
val worldBounds = 0..10
// 移动 - 不会超出边界
player.move(3, 7, worldBounds)
println("Position: (${player.position.x}, ${player.position.y})")
// 输出: Position: (8, 10) 而不是 (8, 12)
// 受到伤害
player.takeDamage(90)
println("Health: ${player.health}") // 输出: Health: 0
// 治疗
player.heal(120)
println("Health: ${player.health}") // 输出: Health: 100
}
3. 数据验证和规范化
data class UserInput(
val username: String,
val age: Int,
val score: Double
) {
companion object {
private val AGE_RANGE = 0..150
private val SCORE_RANGE = 0.0..100.0
private val USERNAME_LENGTH_RANGE = 3..20
}
fun validate(): ValidatedUserInput {
val validatedAge = age.coerceIn(AGE_RANGE)
val validatedScore = score.coerceIn(SCORE_RANGE)
val validatedUsername = if (username.length in USERNAME_LENGTH_RANGE) {
username
} else {
// 截断或处理用户名长度
username.take(USERNAME_LENGTH_RANGE.last)
}
return ValidatedUserInput(validatedUsername, validatedAge, validatedScore)
}
}
data class ValidatedUserInput(
val username: String,
val age: Int,
val score: Double
)
fun main() {
val input1 = UserInput("john_doe", 25, 85.5)
val validated1 = input1.validate()
println(validated1) // 输出: ValidatedUserInput(username=john_doe, age=25, score=85.5)
val input2 = UserInput("jo", 200, 150.0) // 不合规的数据
val validated2 = input2.validate()
println(validated2)
// 输出: ValidatedUserInput(username=jo, age=150, score=100.0)
}
4. 进度和百分比计算
class ProgressTracker(private val totalSteps: Int) {
private var currentStep: Int = 0
fun progress(percent: Int): Float {
// 确保百分比在 0-100 之间
val validPercent = percent.coerceIn(0, 100)
return validPercent / 100f
}
fun advance(steps: Int) {
currentStep = (currentStep + steps).coerceIn(0, totalSteps)
}
fun retreat(steps: Int) {
currentStep = (currentStep - steps).coerceIn(0, totalSteps)
}
val progressPercentage: Int
get() = (currentStep * 100 / totalSteps).coerceIn(0, 100)
}
fun main() {
val tracker = ProgressTracker(50)
tracker.advance(30)
println("Progress: ${tracker.progressPercentage}%") // 输出: Progress: 60%
tracker.advance(40) // 尝试超过总量
println("Progress: ${tracker.progressPercentage}%") // 输出: Progress: 100%
tracker.retreat(100) // 尝试低于 0
println("Progress: ${tracker.progressPercentage}%") // 输出: Progress: 0%
val progress = tracker.progress(150) // 超过 100%
println("Progress: $progress") // 输出: Progress: 1.0
}
5. 颜色值处理
data class Color(val r: Int, val g: Int, val b: Int) {
init {
require(r in 0..255 && g in 0..255 && b in 0..255) {
"颜色值必须在 0-255 之间"
}
}
companion object {
fun fromValues(red: Int, green: Int, blue: Int): Color {
// 使用 coerceIn 确保颜色值在有效范围内
val safeRed = red.coerceIn(0, 255)
val safeGreen = green.coerceIn(0, 255)
val safeBlue = blue.coerceIn(0, 255)
return Color(safeRed, safeGreen, safeBlue)
}
fun darken(color: Color, amount: Int): Color {
val newRed = (color.r - amount).coerceIn(0, 255)
val newGreen = (color.g - amount).coerceIn(0, 255)
val newBlue = (color.b - amount).coerceIn(0, 255)
return Color(newRed, newGreen, newBlue)
}
fun lighten(color: Color, amount: Int): Color {
val newRed = (color.r + amount).coerceIn(0, 255)
val newGreen = (color.g + amount).coerceIn(0, 255)
val newBlue = (color.b + amount).coerceIn(0, 255)
return Color(newRed, newGreen, newBlue)
}
}
}
fun main() {
// 创建颜色,自动处理边界
val color1 = Color.fromValues(100, 200, 300) // 300 会被限制为 255
println(color1) // 输出: Color(r=100, g=200, b=255)
val darker = Color.darken(color1, 150) // 尝试减少 150
println(darker) // 输出: Color(r=0, g=50, b=105)
val lighter = Color.lighten(color1, 200) // 尝试增加 200
println(lighter) // 输出: Color(r=255, g=255, b=255)
}
高级用法
1. 自定义比较类型
// 自定义可比较类型
data class Temperature(val celsius: Double) : Comparable<Temperature> {
override fun compareTo(other: Temperature): Int {
return celsius.compareTo(other.celsius)
}
companion object {
val WATER_FREEZING = Temperature(0.0)
val WATER_BOILING = Temperature(100.0)
val HUMAN_BODY = Temperature(37.0)
}
}
fun main() {
val currentTemp = Temperature(25.0)
val comfortableRange = Temperature(20.0)..Temperature(26.0)
val adjustedTemp = currentTemp.coerceIn(comfortableRange)
println("舒适温度: ${adjustedTemp.celsius}°C") // 输出: 舒适温度: 25.0°C
val hotTemp = Temperature(40.0)
val safeTemp = hotTemp.coerceIn(Temperature.WATER_FREEZING, Temperature.WATER_BOILING)
println("安全温度: ${safeTemp.celsius}°C") // 输出: 安全温度: 40.0°C
}
2. 与集合操作结合
fun main() {
val values = listOf(-10, 5, 15, 25, 35, 45)
val range = 0..30
// 将列表中的所有值限制在范围内
val clampedValues = values.map { it.coerceIn(range) }
println(clampedValues) // 输出: [0, 5, 15, 25, 30, 30]
// 计算平均值,但先限制每个值
val average = values
.map { it.coerceIn(0, 100) }
.average()
println("限制后的平均值: $average") // 输出: 限制后的平均值: 26.666666666666668
}
3. 链式调用
class RangeSlider(private var value: Double, private val range: ClosedRange<Double>) {
fun adjust(amount: Double): RangeSlider {
value = (value + amount).coerceIn(range)
return this
}
fun multiply(factor: Double): RangeSlider {
value = (value * factor).coerceIn(range)
return this
}
fun getValue(): Double = value
}
fun main() {
val slider = RangeSlider(50.0, 0.0..100.0)
val result = slider
.adjust(30.0) // 80.0
.multiply(1.5) // 120.0 → 限制为 100.0
.adjust(-150.0) // -50.0 → 限制为 0.0
.getValue()
println("滑块值: $result") // 输出: 滑块值: 0.0
}
注意事项和边界情况
1. 范围有效性检查
fun main() {
val value = 10
// 范围必须有效(最小值 ≤ 最大值)
try {
val result = value.coerceIn(20, 10) // 最小值 > 最大值
} catch (e: IllegalArgumentException) {
println("错误: ${e.message}")
// 输出: 错误: Cannot coerce value to an empty range: maximum 10 is less than minimum 20.
}
// 正确的做法是确保范围有效
val min = 20
val max = 10
if (min <= max) {
val result = value.coerceIn(min, max)
} else {
// 处理无效范围
println("范围无效: $min > $max")
}
}
2. 空范围处理
fun main() {
val value = 5
// 单点范围(最小值 = 最大值)
val singlePointRange = 5..5
val result1 = value.coerceIn(singlePointRange)
println(result1) // 输出: 5
// 如果值不在单点范围内
val result2 = 10.coerceIn(singlePointRange)
println(result2) // 输出: 5
}
3. 性能考虑
// coerceIn 是内联函数,性能开销很小
// 但在循环中大量使用时,可以考虑手动内联
fun processBatch(values: List<Int>): List<Int> {
val min = 0
val max = 100
// 方式1:使用 coerceIn(推荐,代码清晰)
return values.map { it.coerceIn(min, max) }
// 方式2:手动内联(在极端性能要求时考虑)
// return values.map { if (it < min) min else if (it > max) max else it }
}
// 对于自定义类型,实现 Comparable 接口后即可使用 coerceIn
data class CustomValue(val score: Int) : Comparable<CustomValue> {
override fun compareTo(other: CustomValue): Int = score.compareTo(other.score)
}
最佳实践
1. 防御性编程
class Configuration {
// 使用 coerceIn 确保配置值始终有效
var brightness: Int = 50
set(value) {
field = value.coerceIn(0, 100)
}
var volume: Int = 50
set(value) {
field = value.coerceIn(0, 100)
}
var temperature: Int = 22
set(value) {
field = value.coerceIn(16, 28)
}
}
fun main() {
val config = Configuration()
config.brightness = 120 // 会被限制为 100
println("亮度: ${config.brightness}") // 输出: 亮度: 100
config.temperature = 10 // 会被限制为 16
println("温度: ${config.temperature}") // 输出: 温度: 16
}
2. 结合 when 表达式
fun categorizeScore(rawScore: Int): String {
val score = rawScore.coerceIn(0, 100)
return when (score) {
in 90..100 -> "优秀"
in 80 until 90 -> "良好"
in 60 until 80 -> "及格"
else -> "不及格"
}
}
fun main() {
println(categorizeScore(95)) // 输出: 优秀
println(categorizeScore(120)) // 输出: 优秀(限制为 100)
println(categorizeScore(-10)) // 输出: 不及格(限制为 0)
}
3. 扩展函数增强
// 为数字类型添加扩展函数
fun Int.coerceInPercentage(): Int = this.coerceIn(0, 100)
fun Double.coerceInZeroToOne(): Double = this.coerceIn(0.0, 1.0)
// 为颜色通道值添加扩展
fun Int.coerceInColorChannel(): Int = this.coerceIn(0, 255)
// 使用
fun main() {
val percentage = 150.coerceInPercentage()
println("百分比: $percentage%") // 输出: 百分比: 100%
val alpha = 1.5.coerceInZeroToOne()
println("透明度: $alpha") // 输出: 透明度: 1.0
val red = 300.coerceInColorChannel()
println("红色通道: $red") // 输出: 红色通道: 255
}
总结:
coerceIn是一个简单但强大的函数,用于将值限制在指定范围内- 支持所有实现了
Comparable接口的类型(Int、Double、Char、自定义类型等) - 有两种使用方式:直接指定最小/最大值,或使用
ClosedRange对象 - 适用于:UI 边界检查、游戏开发、数据验证、进度计算等多种场景
- 注意处理无效范围(最小值 > 最大值时会抛出异常)
- 结合扩展函数可以创建更语义化的代码