讲在前面
不可否认,NullPointException 给Java开发者开来的恐惧性和破坏性,Java开发者在使用一个对象之前很难判断该对象是否为空,以至于出现大量判断对象是否为null的代码,如果你漏掉了一处判断,可能你的代码因此而无法运行。
Kotlin 致力于消除空引用所带来的危害,但是Kotlin 100% 兼容Java,所以在和Java交互的时候还是可能出现NullPointException 异常,这点需要注意。
非空对象 NotNullObject
每一个对象默认不可为null(在Kotlin中一切皆对象)
var key = "value" //String 类型
key = null //编译出错
key = "nice" //编译通过
NotNullObject可以任意使用,因为不可能为空的,他不会让你的代码出现NullPointException,放心大胆地用就ok了
可空对象 NullableObject
如果要允许对象为空,我们需要手动声明一个变量为可空类型,在类型后面加多一个问号
var key :String? = "value" //String? 类型
key = null //编译通过
key = "nice" //编译通过
Kotlin编译器在无法判断NotNullObject已经不为空的情况下是不允许你访问他的,那么什么情况下Kotlin 编译器可能判断NotNullObject 不在为空了呢?
if 语句判断
var key :String? = "value" //String? 类型
var chars = key.chars() //编译出错
if(key != null){
var chars = key.chars() //编译通过
}
其实Kotlin 也够笨的,我声明key 的时候已经直接复制为"value",怎么可能还是null 嘛
赋于NotNullObject 值后直接使用
fun getValue(): String = "nice" //定义一个返回NotNullObject 的函数
...
var key :String? = "value" //String? 类型
var chars = key.chars() //编译出错,不能直接使用key对象
key = getValue()
var chars = key.chars() //编译通过
推理得知再次赋于NullableObject 后又无法直接使用
fun getValue(): String = "nice" //定义一个返回NotNullObject 的函数
fun getValue2(): String? = "nice" //定义一个返回NullableObject 的函数
...
var key :String? = "value" //String? 类型
var chars = key.chars() //编译出错,不能直接使用key对象
key = getValue()
var chars = key.chars() //编译通过
key = getValue2()
var chars2 = key.chars() //编译出错,不能直接使用key对象
if 语句的变种方式 ?.
var key :String? = "value" //String? 类型
var chars = key.chars() //编译出错
var chars = key?.chars() //编译通过,返回的对象也是NullableObject
//反编译得出的Java代码是
IntStream chars = key != null?key.chars():null;
可空对象转非空对象 NullableObject->NotNullObject
有时候我们知道在某种条件返回NullableObject 的函数一定不会为null,那么我们就希望使用NullableObject 来保存,方便下面代码使用的时候不需要反派对象是否为null,而可以直接使用,Kotlin 提供了as 操作符可以转换
fun getValue2(): String? = "nice" //定义一个返回NullableObject 的函数
...
var key: String = getValue2() //编译不通过,无法把String? 赋予String
var key: String = getValue2() as String //编译不通过,as 强行转为String 类型
//使用as 后可以让Kotlin 编译器推到出类型
var key = getValue2() as String //key 同样为String 类型
var chars = key.chars() //编译通过,可以直接使用
引发的问题:使用对象之前必须能推断已经初始化了
全局变量必须在声明的时候就初始化
//下面代码不包含在类里面,或者函数里面,他是一个全局变量
var key2:String? //编译出错,属性必须初始化
var key2:String? = null //编译通过
//同理
var key2:String //编译出错,全局变量必须初始化
var key2:String = "value" //编译通过
类的属性必须在调用init{} 后初始化完成
//1
class Key{
var key:String? //编译出错,属性必须初始化
}
//2
class Key{
var key:String?="value" //编译通过
}
//3
class Key(value:String){
var key:String?=value //编译通过
}
//4 编译通过
class Key(value:String){
var key:String?
init {
key = value
}
}
如果是val 对象那么初始化的方式还有一种,叫自定义getter
//编译通过
val key: String
get() = "value"
局部变量需要让Kotlin编译器知道已经初始化了才能使用
//1
fun getValue(): String? {
var key: String?
return key //编译出错,局部变量必须初始化
}
//2
fun getValue(): String? {
var key: String? = null //赋予其他值也可以
return key //编译通过
}
//3 够笨的,3==3 也不知道
fun getValue(): String? {
var key: String?
if(3 == 3){
key = "value"
}
return key //编译出错,Kotlin编译器无法推断是否已经初始化了
}
//4
fun getValue(): String? {
var key: String?
if(3 == 3){
key = "value"
}else{
key = "value2"
}
return key //编译通过
}
回忆一下以前的java类的定义吧,加上对比
//java
public class User {
private int id;
private String nickName;
private String avatar;
}
//kotlin
class User{
private var id:Int = 0
private var nickName:String? = null
private var avatar:String? = null
}
延迟初始化属性 lateinit
面对有生命周期的编程,例如Android 的Activity、Fragment,这些类的属性不是对象被创建了就能初始化了,需要在特定生命周期方法上面才能初始化了,但我有不想生命为NullableObject 初始化为null,不然以后使用都要判断是否为null,那么更麻烦了。Kotlin 的解决方案是使用 lateinit 延迟初始化属性。
使用注意点
- 只有NotNullObject 才能使用lateinit
- 创建对象不必立马初始化lateinit属性
- 什么时候初始化由开发者决定
- 使用该属性和使用NotNullObject 是一样的,是否为null 由开发者保证
//编译通过
class User{
private lateinit var nickName:String
}
讲在最后
Kotlin 和Java交换无法感知方法返回的类型是否为NullableObject 还是NotNullObject 的时候,Kotlin会使用一种感叹号类型方式,感叹以前Java 的无知,当确定不可能会null 的话就会返回String 类型,当无法确保的话,那么就返回String! 类型,Kotlin只能帮到这里了。
开发者的做法:如果开发者确定该方法返回的类型一定是NotNullObject,那么请正常使用它,如果开发者也无法确定,那么请使用NullableObject 来保存他。
image.pngfun main(args: Array<String>) {
var key = "value"
var chars = key.chars() //假设我们确定一定不为null
}
fun main(args: Array<String>) {
var key = getValue()
var chars: IntStream? = key.chars()//假设我们无法确定是否为null
}
网友评论