《Kotlin核心编程》笔记:可空类型&平台类型&装箱类型&数组类型&泛型&协变与逆变

可空类型

在Kotlin中,我们可以在任何类型后面加上“?”,比如“Int?”,实际上等同于“Int? = Int or null”。

通过合理的使用,不仅能够简化很多判空代码,还能够有效避免空指针异常。

注意:由于null只能被存储在 Java 的引用类型的变量中,所以在 Kotlin 中基本数据的可空版本都会使用该类型的包装形式。同样,如果你用基本数据类型作为泛型类的类型参数,Kotlin同样会使用该类型的包装形式。(即可空类型会自动装箱)

Java中对于null的一些解决方案:

  • 函数内对于无效值,可以抛异常处理。
  • 采用@NotNull/@Nullable标注。
  • 使用专门的Optional对象对可能为null的变量进行装箱。

可空类型相关的安全操作符

安全的调用 ?.

s.student?.glasses?.degreeOfMyopia

Elvis操作符 ?:

val result = student.glasses?.degreeOfMyopia ?: -1

又称合并运算符

非空断言 !!

val result = student!!.glasses

类型检查

在Kotlin中,我们可以用“is”来判断。

if (obj is String) {
   
    print(obj.length)
}

if (obj !is String) {
    // 等同于 !(obj is String)
    print("Not a String")
} else {
   
    print(obj.length)
}

when (obj) {
   
    is String -> print(obj.length)
    !is String -> print("Not a String")
}

类型智能转换

Smart Casts 可以将一个变量的类型转变为另一种类型,它是隐式完成的。

val stu: Any = Student(Glasses(189.00)) 
if(stu is Student) println(stu.glasses) 

对于可空类型,我们可以使用 Smart Casts:

val stu: Student = Student(Glasses(189.00))
if (stu.glasses != null) println(stu.glasses.degreeOfMyopia) 

我们将这个例子反编译成Java,核心代码如下

...
Intrinsics.checkParameterlsNotNull(args, "args"); 
Student stu = new Student(new Glasses(189.0D)); 
if (stu instanceof Student) {
   
	Glasses var2 = ((Student)stu).getGlasses(); 
	System.out.println(var2);
}
...

我们可以看到,这与我们写的Java版本一致,这其实是Kotlin的编译器帮我们做出了转换。

根据官方文档介绍:当且仅当Kotlin的编译器确定在类型检查后该变量不会再改变,才会产生SmartCasts。

利用这点,我们能确保多线程的应用足够安全。举个例子:

class Kot {
   
    var stu: Student? = getStu()
    
    fun dealStu() {
   
        if (stu != null) {
   
            print(stu.glasses)
        }
    }
}

上述代码中,我们将stu声明为引用可空类型变量,这意味着在判断 stu != null 之后,stu在其他线程中还是会被修改的,所以被编译器无情地拒绝了。

var改为val就不会存在这样的问题,引用不可变能够确保程序运行不产生额外的副作用。你也许会觉得这样写不够优雅,我们可以用let函数来简化⼀下:

class Kot {
   
    var stu: Student? = getStu()
    
    fun dealStu() {
   
        stu?.let {
    print(it.glasses) }
    }
}

这样就会满足 Smart Casts 的条件,就不会被拒绝编译。

在实际开发中,我们并不总能满足 Smart Casts 的条件。并且 Smart Casts 有时会缺乏语义,并不适用于所有场景。

当类型需要强制转换时,我们可以利用“ as” 操作符来实现。

class Kot {
   
    val stu: Student? = getStu() as Student?
    
    fun dealStu() {
   
        if (stu != null) {
   
            print(stu.classes)
        }
    }
}

由于val只允许赋值一次,这样,我们在外部已经确定了stu的类型,当stu不为空时,在dealStu方法里就可以成功调用stu的参数。

注意:

  • 这里stu变量是在dealStu()方法的外面,必须使用val生声明,否则如果是var,仍然可能有线程安全问题所以也会被拒绝编译。
  • 如果stu变量是放在dealStu()方法的里面,那么可以使用var, 因为是方法本地局部变量,会被线程独占,此时也满足 Smart Casts 的条件。

因为getStu可能为空,如果我们将其转换类型改为 Student

val stu: Student? = getStu() as Student

则会抛出类型转换失败的异常,因为它不是可空的。所以,我们通常称之为“ 不安全” 的类型转换。

那是否有安全版本的转换呢?除了上述写法外,Kotlin还提供了操作符“ as?”,我们可以这样改写:

val stu: Student? = getStu() as? Student

这时,如果stu为空将不会抛出异常,而是返回转换结果null

Any:非空类型的根类型

Object 作为 Java 类层级结构的顶层类似,Any 类型是 Kotlin 中所有非空类型(如StringInt)的超类:

在这里插入图片描述

与 Java 不同的是,Kotlin 不区分“原始类型”(primitivetype)和其他的类型,它们都是同一类型层级结构的一部分。

如果定义了一个没有指定父类型的类型,则该类型将是Any的直接子类型。如:

class Animal(val weight: Double) 

如果你为定义的类型指定了父类型,则该父类型将是新类型的直接父类型,但是新类型的最终根类型为Any

另外,Kotlin 把 Java 方法参数和返回类型中用到的 Object 类型看作 Any(更确切地说是当作“平台类型”)。当在 Kotlin 函数中使用 Any 时,它会被编译成 Java 字节码中的 Object

什么是平台类型?

平台类型本质上就是 Kotlin 不知道可空性信息的类型,所有 Java 引用类型在 Kotlin 中都表现为平台类型。当在 Kotlin 中处理平台类型的值的时候,它既可以被当作可空类型来处理,也可以被当作非空类型来操作。

在这里插入图片描述

平台类型的引入是 Kotlin 兼容 Java 时的一种权衡设计。试想下,如果所有来自 Java 的值都被看成非空,那么就容易写出比较危险的代码。反之,如果 Java 中的值都强制当作可空,则会导致大量的 null 检查。综合考量,平台类型是一种折中的设计方案。

Any?:所有类型的根类型

如果说Any是所有非空类型的根类型,那么 Any? 才是所有类型(可空和非空类型)的根类型。
AnyAny? 看起来没有继承关系,然而在我们需要用 Any? 类型值的地方,显然可以传入一个类型为Any的值,这在编译上不会产生问题。比如在 Kotlin 中 IntNumber 的子类:

fun printNum(num : Number){
   
    println(num)
}

val n : Int = 1
printNum(n)

反之却不然,比如一个参数类型为Any的函数,我们传入符合 Any? 类型的null值,就会出现如下的错误:

error
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

川峰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值