类型检查、转换与相等性¶
Kotlin 的类型检查和转换比 Java 更依赖编译器推断。你经常会看到 is、as?、智能转换和 ==,它们看起来简单,但理解细节能避免很多迁移错误。
is 与 !is¶
使用 is 判断运行时类型:
fun printLength(value: Any) {
if (value is String) {
println(value.length)
}
}
在 if 分支中,value 被智能转换为 String,无需像 Java 那样再强制转换。
Java 对比:
void printLength(Object value) {
if (value instanceof String text) {
System.out.println(text.length());
}
}
取反判断:
if (value !is String) return
println(value.length)
编译器知道 return 之后还能继续执行时,value 一定是 String。
when 中的类型检查¶
fun describe(value: Any): String =
when (value) {
is String -> "字符串长度 ${value.length}"
is Int -> "整数 ${value + 1}"
is List<*> -> "列表大小 ${value.size}"
else -> "未知"
}
每个分支中都会根据类型自动智能转换。
智能转换的前提¶
智能转换只在编译器能证明变量不会在检查后被改变时成立。
通常可用:
- 局部
val。 - 未被修改的局部
var。 - 某些同模块、不可覆盖、没有自定义 getter 的
val属性。
通常不可用:
- 可变属性
var。 open属性。- 有自定义 getter 的属性。
- 被 Lambda 捕获并可能修改的变量。
示例:
class User(var name: String?)
fun printName(user: User) {
if (user.name != null) {
// println(user.name.length) // 可能无法智能转换
}
}
因为 user.name 是可变属性,编译器不能保证它在检查后没有变化。常见写法是复制到局部变量:
val name = user.name
if (name != null) {
println(name.length)
}
as:不安全转换¶
val value: Any = "Kotlin"
val text = value as String
如果类型不匹配,会抛出 ClassCastException:
val number = value as Int // 运行时异常
只在你确定类型正确时使用 as。
as?:安全转换¶
val value: Any = "Kotlin"
val number: Int? = value as? Int
转换失败返回 null,不会抛异常。配合 Elvis 运算符很常见:
fun stringLength(value: Any): Int {
val text = value as? String ?: return -1
return text.length
}
处理外部输入、反序列化结果、Java API 返回值时,优先考虑 as?。
向上转型与向下转型¶
向上转型通常无需显式写:
interface Animal {
fun speak()
}
class Dog : Animal {
override fun speak() = println("woof")
fun bark() = println("bark")
}
val animal: Animal = Dog()
向下转型需要显式操作:
val dog = animal as? Dog
dog?.bark()
如果你经常需要向下转型,可能说明抽象类型设计不足。优先考虑多态方法、密封类 when 或访问者模式。
结构相等:==¶
Kotlin 的 == 调用的是 equals(),并且安全处理 null:
val a: String? = "hello"
val b: String? = "hello"
println(a == b) // true
a == b 大致等价于:
a?.equals(b) ?: (b === null)
这与 Java 很不同。Java 中 == 比较引用,equals() 比较结构;Kotlin 中 == 默认就是结构相等。
引用相等:===¶
=== 判断两个引用是否指向同一个对象:
val a = mutableListOf(1)
val b = a
val c = mutableListOf(1)
println(a === b) // true
println(a === c) // false
println(a == c) // true
普通业务代码大多使用 ==。只有需要判断同一实例时才使用 ===。
自定义 equals¶
普通类默认继承 Any.equals(),通常是引用相等。数据类会自动根据主构造函数属性生成结构相等。
普通类需要自定义结构相等时:
class Point(val x: Int, val y: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Point) return false
return x == other.x && y == other.y
}
override fun hashCode(): Int =
31 * x + y
}
重写 equals() 时必须同步重写 hashCode(),否则在 HashSet、HashMap 中会出现错误行为。
数组相等¶
数组的 == 比较数组对象本身,不比较内容。比较内容要使用:
val a = intArrayOf(1, 2)
val b = intArrayOf(1, 2)
println(a == b) // false
println(a.contentEquals(b)) // true
嵌套数组使用 contentDeepEquals()。
浮点相等¶
当表达式静态类型是 Float 或 Double 时,相等性遵循 IEEE 754。涉及 NaN、-0.0、0.0 时要格外小心。
实践中不要用浮点数直接表达金额,也不要在精度敏感计算中直接用 == 判断结果是否相等。
实践建议¶
- 类型不确定时优先
as?,少用as。 - 智能转换失败时,先复制到局部
val,不要立刻写!!。 - Kotlin 中
==是结构相等,Java 迁移时尤其要注意。 - 判断同一实例才用
===。 - 自定义
equals()必须同步自定义hashCode()。 - 数组内容比较使用
contentEquals(),不是==。
参考¶
- 官方类型检查与转换:https://kotlinlang.org/docs/typecasts.html
- 官方相等性:https://kotlinlang.org/docs/equality.html