类型检查转换:is和as
Kotlin官网:Other-Type Checks and Casts
is和!is运算符
要在运行时检查某一对象是否为某个类型使用is
运算符判断,相符is
,不相符!is
:
if (obj is String) {
print(obj.length)
}
if (obj !is String) { // same as !(obj is String)
print("Not a String")
}
else {
print(obj.length)
}
自动转换
某些场景下,Kotlin的编译器可以判断出类型转换是安全的,此时无需显式地转换,可以直接作为对应类型使用,编译器会自动插入适当的转换:
例如is检查后:
fun demo(x: Any) {
if (x is String) {
print(x.length) // x自动转换为String
}
}
!is检查后return中断后面代码:
if (x !is String) return
print(x.length) // x自动转换为String
短路运算的右侧:
// x is automatically cast to string on the right-hand side of `||`
if (x !is String || x.length == 0) return
// x is automatically cast to string on the right-hand side of `&&`
if (x is String && x.length > 0) {
print(x.length) // x is automatically cast to String
}
when表达式和while循环中也可以:
when (x) {
is Int -> print(x + 1)
is String -> print(x.length + 1)
is IntArray -> print(x.sum())
}
当编译器无法判断类型转换是否安全时,自动转换就失效了。使用自动转换的规则如下:
- val局部变量:除委托属性外都可以;
- val属性:private/internal/同一module中检查过,自动转换生效;
当声明为公开的/有自定义getter时自动转换失效; - var局部变量:检查完后立刻使用,中间没有被修改,没有在lambda中修改,不是局部委托属性;
- var属性:无法使用自动转换,任何时候都不行。
不安全的转换
使用as
运算符转换,如果类型不符会抛出异常,认为是不安全的:
val x: String = y as String
注意Kotlin区分可空和不可空类型,所以null转换成不可空类型也会抛出异常,如上例y为null时会出错。null可以转换为可空类型:
val x: String? = y as String?
安全(可空)转换
为了防止为空时转换出现异常,可以用as?
运算符,为null时返回null:
val x: String? = y as? String
as?运算符的右侧为不可空类型,返回值为可空类型。
类型擦除和泛型类型检查
泛型只在编译时保证类型安全,编译后会被擦除,运行时无法获取泛型信息,具体见3-8泛型。
星号除外,类型投射可以用来做类型检查:
if (something is List<*>) {
something.forEach { println(it) } // The items are typed as `Any?`
}
带泛型的类自身可以用来检查和转换,泛型的尖括号可以省略:
fun handleStrings(list: List<String>) {
if (list is ArrayList) {
// `list` is smart-cast to `ArrayList<String>`
}
}
转换:list as ArrayList
。
具体化类型参数的内联函数会在运行时保留泛型信息,arg is T
是允许的(T是泛型),泛型实例的类型信息依然会被擦除:
inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
if (first !is A || second !is B) return null
return first as A to second as B
}
val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)
val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // Breaks type safety!
fun main(args: Array<String>) {
println("stringToSomething = " + stringToSomething)
println("stringToInt = " + stringToInt)
println("stringToList = " + stringToList)
println("stringToStringList = " + stringToStringList)
}
未检查的转换
例:
fun readDictionary(file: File): Map<String, *> = file.inputStream().use {
TODO("Read a mapping of strings to arbitrary elements.")
}
// We saved a map with `Int`s into that file
val intsFile = File("ints.dictionary")
// Warning: Unchecked cast: `Map<String, *>` to `Map<String, Int>`
val intsDictionary: Map<String, Int> = readDictionary(intsFile) as Map<String, Int>
程序逻辑保证了类型转换安全,但是Kotlin无法识别,此时会有警告。
在上例中,定义带泛型的类,可以避免调用者进行未检查的转换,将转换逻辑移到具体实现中。
要消除警告,可以使用@Suppress("UNCHECKED_CAST")
注解:
inline fun <reified T> List<*>.asListOfType(): List<T>? =
if (all { it is T })
@Suppress("UNCHECKED_CAST")
this as List<T> else
null
在JVM平台,数组类(Array<Foo>
)会保留元素的类型信息,类型转换检查部分生效,元素类型包含的泛型信息依然会被擦除。例如:foo as Array<List<String>?>
,foo可以被转换成任何List<*>
型数组,List的泛型信息被擦除了。