可空性
可控性主要的内容是几个云算法的运用:?
、?.
、?:
、as?
、let
、!!
、lateinit
这几个运算符或者关键字的使用。值得一提的是,有一种让你惊讶的情况:即使不用问号结尾,类型参数也能是可空的。
fun printHashCode(t: T) {
// 因为"t"可能为 null,所以必须使用安全调用
println(t?.hashCode())
}
// "T"被推导成"Any?"
>>> printHashCode(null)
null
在printHashCode
调用中,类型参数T
推导出的类型是可空类型Any?
。因此,尽管没有用问号结尾,实参t
依然允许持有null
。
要使类型参数非空,你必须要为它指定一个非空的上界。那样泛型会拒绝可空值作为实参。
// 现在"T"就不是可空的
fun printHashCode(t: T) {
println(t.hashCode())
}
# 这段代码是无法编译的:你不能传递 null,因为期望的是非空值
>>> printHashCode(null)
Error: Type parameter bound for `T` is not satisfied
>>> printHashCode(42)
42
可空性和 Java
有些时候 Java 代码包含了可空性的信息,这些信息使用注解来表达。Java 中的@Nullable String
被 Kotlin 当作String?
,而@NotNull String
就是String
。
Kotlin 可以识别多种不同风格的可空性注解,包括 JSR-305 标准的注解(在javax.annotation
包之中),Android 的注解(android.support.annotation
),和 JetBrains 工具支持的注解(org.jetbrains.annotations
)。这里有一个有意思的问题,如果这些注解不存在会发生什么?这种情况下,Java 类型会变成 Kotlin 中的平台类型。
平台类型本质上就是 Kotlin 不知道可空性信息的类型。你既可以把它当作可空类型处理,也可以当作非空类型处理。这意味着,你要像在 Java 中一样,对你在这个类型上做的操作负有全部责任。编译器将会允许所有的操作,它不会把对这些值的空安全操作高亮成多余的。
在 Kotlin 中你不能声明一个平台类型的变量,这些类型只能来自 Java 代码。但你可能会在 IDE 的错误消息里见到它们:
>>> val i: Int = person.name
ERROR: Type mismatch: inferred type is String! but Int was expected
String!
表示法被 Kotlin 编译器用来表示来自 Java 代码的平台类型。你不能在自己的代码里使用这种语法。而且感叹号通常与问题的来源无关,所以通常可以忽略它。它只是强调类型的可空性是未知的。
继承Java类型
当在 Kotlin 中重写 Java 的方法时,你可以选择把参数和返回类型定义成可空的,也可以选择把它们定义成非空的。
/* Java */
interface StringProcessor {
void process(String value);
}
class StringPrinter : StringProcessor {
override fun process(value: String) {
println(value)
}
}
class NullableStringPrinter : StringProcessor {
override fun process(value: String?) {
if (value != null) {
println(value)
}
}
}
注意在实现 Java 类或者接口的方法时一定要搞清楚它的可空性。因为方法的实现可以在非 Kotlin 的代码中被调用,Kotlin 编译器会为你声明的每一个非空的参数生成非空断言。如果 Java 代码传给这个方法一个null
值,断言将会触发,你会得到一个异常,即便你从没有在你的实现里访问过这个参数的值。
基本数据类型
Any 和 Any?
Any
类型是 Kotlin 所有非空类型的超类型(非空类型的根)。在 Kotlin 中如果你需要可以持有任何可能值的变量,包括null
在内,你必须使用Any?
类型。
在底层,Any
类型对应java.lang.Object
。Kotlin 把 Java 方法参数和返回类型中用到的Object
类型看作Any
。(更确切地是当作平台类型,因为其可空性是未知的)。当 Kotlin 函数使用Any
时,它会被编译成 Java 字节码中的Object
。
Unit 类型:Kotlin 的“void”
fun f(): Unit { ... }
// 显式的 Unit 声明被省略了
fun f(): { ... }
Unit
是一个完备的类型,可以作类型参数,而void
却不行。只存在一个值是Unit
类型,这个值也叫作Unit
,并且(在函数中)会被隐式地返回。
Nothing 类型: “这个函数永不返回”
fun fail(message: String): Nothing {
throw IllegalStateException(message)
}
集合与数组
Kotlin 与 Java 集合是可以相互转换的,至于集合的可控性需要使用时自行判断。
数组
如果你声明了一个Array
,它将会是一个包含装箱整型的数组(它的 Java 类型将是java.lang.Integer[]
)。如果你需要创建没有装箱的基本数据类型的数组,必须使用一个基本数据类型数组的特殊类。Int
类型值的数组叫做IntArray
。 Kotlin 还提供了ByteArray
、CharArray
、BooleanArray
等等给其他类型。所有这些类型都被编译成普通的 Java 基本数据类型数组,比如int[]
、byte[]
、char[]
等等。
val fiveZeros = IntArray(5)
val fiveZerosToo = intArrayOf(0, 0, 0, 0, 0)
val squares = IntArray(5) { i -> (i+1) * (i+1) }