转载

Kotlin知识归纳(六) —— 类型系统

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

可空类型与非空类型

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

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

val str:String? = null
复制代码

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

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

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

Kotlin的可空性与Java的Optional

Java8中引入的特殊包装类型Optional来解决null引用问题。但这种方法使代码更加冗长,并且额外的包装类还影响运行时的性能,因此并没有被广泛使用起来。

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

安全调用运算符:?.

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

Kotlin知识归纳(六) —— 类型系统

安全调用除了可以调用方法,还可以用来访问属性。

Elvis运算符:?:

Elvis运算符?:用来提供替代null的默认值。Elvis运算符接收两个表达式,如果左侧表达式非空,则返回其左侧表达式。当左侧表达式为空,则返回右侧表达式。

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:

Kotlin知识归纳(六) —— 类型系统

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){
    
}
复制代码
原文  https://juejin.im/post/5cefa6d7f265da1bac3ffba4
正文到此结束
Loading...