Kotlin知识概括(六) —— 类型系统

前序

      Kotlin引入可空性的新特性,旨在消除来自代码空引用的危险。将运行时的NPE转变成编译器的错误。android

可空类型与非空类型

      在Kotlin类型系统中,分为可空类型和非空类型。当你容许一个变量为null时,须要显示在类型后面加上一个问号,将其非空类型转换为可空类型。安全

      常见的类型都是非空类型,不能存储null引用,只有在类型后面添加个问号转换为可空类型后,变量才可存储null引用。bash

val str:String? = null
复制代码

      对于一个可空类型的值,不能直接调用该类型的方法,也不能把他赋值给非空类型,更不能把它传递给接受非空类型参数的函数。可空类型看似和非空类型并无什么交互性,但其实并非,只是须要对可空类型进行一个判空后,才能正常交互:函数

val str:String? = “”
if (str != null)
    str.length
复制代码

      一旦对可空类型的对象进行判空,编译器就会对判空的做用域内把该对象看成非空对待。post

Kotlin的可空性与Java的Optional

      Java8中引入的特殊包装类型Optional来解决null引用问题。但这种方法使代码更加冗长,而且额外的包装类还影响运行时的性能,所以并无被普遍使用起来。性能

      但在Kotlin中,可空和非空的对象在运行时没有什么区别,可空类型并非非空类型的包装类。全部的检查都是编译器完成,这使得Kotlin的可空类型并不会在运行时带来额外的开销。ui

安全调用运算符:?.

      Kotlin标准库中有一个高效的安全调度运算符:?. 。它将null检查和调用合并成一个操做。当你使用?.调用一个可空类型对象的方法时,若值不为空,则方法会被正常执行;若值为null,则方法调用不发生,并整个表达式返回null。this

安全调用除了能够调用方法,还能够用来访问属性。spa

Elvis运算符:?:

      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)
复制代码

      因此只有确保该可空类型对象不为空时,才使用非空断言。当使用非空断言并且发生异常时,异常栈只代表异常发生在哪一行,并不会指明哪一个表达式,因此最好避免同一行中使用非空断言。

安全转换:as?

      和常规的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函数

      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"
    }
}
复制代码

可空性与Java

      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){
    
}
复制代码

参考资料:

android Kotlin系列:

Kotlin知识概括(一) —— 基础语法

Kotlin知识概括(二) —— 让函数更好调用

Kotlin知识概括(三) —— 顶层成员与扩展

Kotlin知识概括(四) —— 接口和类

Kotlin知识概括(五) —— Lambda

Kotlin知识概括(六) —— 类型系统

Kotlin知识概括(七) —— 集合

Kotlin知识概括(八) —— 序列

Kotlin知识概括(九) —— 约定

Kotlin知识概括(十) —— 委托

Kotlin知识概括(十一) —— 高阶函数

Kotlin知识概括(十二) —— 泛型

Kotlin知识概括(十三) —— 注解

Kotlin知识概括(十四) —— 反射

相关文章
相关标签/搜索