Kotlin引入可空性的新特性,旨在消除来自代码空引用的危险。将运行时的NPE转变成编译器的错误。android
在Kotlin类型系统中,分为可空类型和非空类型。当你容许一个变量为null时,须要显示在类型后面加上一个问号,将其非空类型转换为可空类型。安全
常见的类型都是非空类型,不能存储null引用,只有在类型后面添加个问号转换为可空类型后,变量才可存储null引用。bash
val str:String? = null
复制代码
对于一个可空类型的值,不能直接调用该类型的方法,也不能把他赋值给非空类型,更不能把它传递给接受非空类型参数的函数。可空类型看似和非空类型并无什么交互性,但其实并非,只是须要对可空类型进行一个判空后,才能正常交互:函数
val str:String? = “”
if (str != null)
str.length
复制代码
一旦对可空类型的对象进行判空,编译器就会对判空的做用域内把该对象看成非空对待。post
Java8中引入的特殊包装类型Optional来解决null引用问题。但这种方法使代码更加冗长,而且额外的包装类还影响运行时的性能,所以并无被普遍使用起来。性能
但在Kotlin中,可空和非空的对象在运行时没有什么区别,可空类型并非非空类型的包装类。全部的检查都是编译器完成,这使得Kotlin的可空类型并不会在运行时带来额外的开销。ui
Kotlin标准库中有一个高效的安全调度运算符:?. 。它将null检查和调用合并成一个操做。当你使用?.调用一个可空类型对象的方法时,若值不为空,则方法会被正常执行;若值为null,则方法调用不发生,并整个表达式返回null。this
安全调用除了能够调用方法,还能够用来访问属性。spa
Elvis运算符?:用来提供替代null的默认值。Elvis运算符接收两个表达式,若是左侧表达式非空,则返回其左侧表达式。当左侧表达式为空,则返回右侧表达式。.net
Elvis运算符常常与安全调度运算符一块儿使用:
val str:String? = null
println(str?.length ?: 0)
复制代码
Elvis运算符也能够配合return 和 throw一块儿使用,当运算符左边为null时,能提早返回函数或抛出异常。
val str:String? = null
//为空抛一次
val length = str?.length ?: throw IllegalArgumentException()
println(length)
复制代码
str?.let {
println(length)
} ?: return
//等价于
if(str == null)
//函数类型为空时直接打断函数继续执行
return
//str不为null,则继续执行。
println(length)
复制代码
也能够配合run函数配合使用,替代if-lese:
str?.let {
//str不为空的逻辑
} ?: run {
//str为空时逻辑
}
复制代码
Kotlin为NPE爱好者提供非空断言运算符 !! (双感叹号),能够把任何对象转换成非空类型,从而调用该对象方法,但可能形成抛出NPE。
val str:String? = null
//抛NPE
println(str!!.length)
复制代码
因此只有确保该可空类型对象不为空时,才使用非空断言。当使用非空断言并且发生异常时,异常栈只代表异常发生在哪一行,并不会指明哪一个表达式,因此最好避免同一行中使用非空断言。
和常规的Java转换同样,当被转换的值不是你视图转换的类型时,会抛出ClassCastException异常。通常解决方案是在使用在转换前使用is检查来肯定该值是否符合转换类型。但Kotlin提供更简洁的运算符——安全转换运算符:as?
//定义父类和子类
open class Animal{
fun getName(){
}
}
class Dog:Animal(){
fun getDogName(){
}
}
fun main(args:Array<String>){
val animal:Animal = Dog()
val dog = animal as? Dog ?: return
dog.getDogName()
}
复制代码
安全转换运算符尝试将值转换成给定的类型,不然返回null:
let函数将调用它的对象变成lambda表达式的参数。配合安全调度运算符能够把调用let函数的可空对象,转变成非空类型。而后在let函数中调用一系列对该可空类型的操做。
fun main(args:Array<String>){
val str:String? = null
str?.let {
daqi(it)
}
}
fun daqi(str:String){
}
复制代码
当须要检查多个值是否为null时,不建议使用嵌套的let调用来处理,建议使用一个if语句对这些值进行一次性检查。
对可空类型的进行扩展的好处是,容许接收者为null时调用扩展函数,并在扩展函数中处理null,而不用确保变量不为null后再调用该对象的方法。由于当实例为null时,成员方法永远不会被执行。
Kotlin标准库中的CharSequence存在两个扩展函数:isNullOrEmpty和isNullOrBlank,能够由String?类型的接收者调用。
对可空类型定义扩展函数时,意味着函数体中的this可能为空,须要作对应的空处理。
fun String?.daqi(){
if (this == null){
println("this is null")
}
}
fun main(args:Array<String>){
val str:String? = null
因为接收的是可空类型,不须要使用?.
str.daqi()
}
复制代码
Kotlin中,属性声明为非空类型时,必须在构造函数中初始化。但属性能够在一个特殊的方法中,经过依赖注入来初始化。这时不能在构造函数中为属性提供一个非空初始化器,但你仍想将该类型声明为非空类型,避免空检查。可使用lateinit关键字修饰该变量,请将该变量使用var修饰,由于val必须会编译成必须在构造方法中初始化的final字段。
class daqi{
private lateinit var name:String
fun onCreate(){
name = "daqi"
}
}
复制代码
Kotlin会根据Java中的可空性注解,来对来自Java的类型分为可空类型和非空类型。如,@Nullable注解的对象,会被Kotlin看成可空类型的对象。@Notnull注解的对象,会被Kotlin看成非空类型的对象。
当可空性注解不存在时,Java类型会被转换为Kotlin的平台类型。平台类型本质上是Kotlin不知道其可空信息,既能够把它看成可空类型,也能够把它看成非空类型。若是选择非空类型,编译器会在赋值时触发一个断言,防止Kotlin的非空变量保存空值。这意味着须要开发者负责正确处理来自Java的值。
Kotlin定义的函数中,编译器会生成对每一个非空类型的参数的检查,若是使用不正确的参数调用,会当即抛出异常。(这种检查在函数调用的时候就被执行了,而不是等到该异常参数被使用时才执行。)
Java区分基本数据类型和引用类型,基本数据类型具备高效存储和传递的性质。当你须要在泛型类中存储一些基本数据类型时,须要以基本数据类型的包装类型进行存储。由于JVM不支持用基本数据类型做为类型参数。
Kotlin并不区分基本类型和包装类型。对于变量、属性和返回类型,Kotlin的基本数据类型会被编译成Java的基础数据类型。只有对于泛型类时,才会被编译器成对应的Java基本类型包装类。
当使用Java声明的基本数据类型变量时,该类型会变成非空类型,而不是平台类型。由于Java的基本数据类型不能存储null值。
Kotlin中可空的基本数据类型会被编译成对应的包装类型,由于Java的基本数据类型不能存储null值。
kotlin不会自动把数字从一种类型转换成另外一种取值范围更大的类型。Kotlin为每种基本数据类型(Boolean除外)都定义了转换到其余基本数据类型的函数。
Kotlin要求转换必须显式的,由于在Java中,比较装箱值时,不只检查他们存储的值,还会比较装箱类型。
//此处比较会返回false
new Integer(42).equals(new Long(42))
复制代码
Kotlin标准库为字符串也提供了转换基本数据类型的扩展函数。若是对字符串解析失败,则抛出NumberFormatException()方法。
Any类型是全部Kotlin非空类型的超类。但Any不能持有null值,当须要持有任何值的变量包括null值,必须使用Any?
Any只包含toString、equals和hashCode。全部Kotlin的这些方法都是从Any中继承来得。但Any不能使用使用其余Object的方法(如:wait和notify)
Kotlin中因此泛型类和泛型函数的类型参数默认都是可空的,由于默认上界是Any?
若是须要类型参数非空,则必须为其指定一个非空的上界:
fun <T:Any> daqi(t:T){
}
复制代码