小心,在数据类当中用 Lazy 要谨慎!

1.数据类中使用 lazy 遇到坑

话说呀,数据类本来设计出来就应该是一种纯数据结构,可偏偏它也是一个类,所以我们自然可以为它定义各种成员,甚至扩展,通常来说这倒也不是什么问题。不过如果我们定义了需要在主构造器中执行的代码,那么就可能会有点儿麻烦了。

  1. data class Person(val name: String, val age: Int){

  2.    val lastName: String by lazy {

  3.        name.split(" ").last()

  4.    }

  5.    val firstName: String by lazy {

  6.        name.split(" ").first()

  7.    }

  8. }

假设构造 Person 这个类的时候传入的 name 都是标准的 DonaldTrump 这样的格式,所以下面的代码理论上是可以拿到普爷的名和姓的:

  1. val trump = Person("Donald Trump", 71)

  2. println(trump.firstName)

输出的就是:

  1. Donald

那么问题来了,一般来说数据类都是免不了要序列化和反序列化的,所以有可能普爷是从硬盘上来的:

  1. val trump = Gson().fromJson(json, Person::class.java)

  2. println(trump.firstName)

这个 json 长这样:

  1. {

  2.    "name": "Donald Trump",

  3.    "age": 71

  4. }

那么结果呢?

  1. Exception in thread "main" java.lang.NullPointerException

  2.    at com.bennyhuo.kotlin.ext4nulls.Person.getFirstName(main.kt)

  3.    at com.bennyhuo.kotlin.ext4nulls.MainKt.main(main.kt:12)

2. 敢问坑来自何方?

什么情况。。怎么就出了空指针了呢?

原因是 Person 这个类没有无参构造方法,所以 Gson 会用 Unsafe 去实例化它,这样的话主构造器就被跳过了。我们看下抛异常的位置等效的 Java 代码:

  1. public final String getFirstName() {

  2.  Lazy var1 = this.firstName$delegate;

  3.  KProperty var3 = $$delegatedProperties[1];

  4.  return (String)var1.getValue();

  5. }

而这个 firstName$delegate 实际上只在主构造器调用时才初始化了:

  1. this.firstName$delegate = LazyKt.lazy((Function0)(new Function0() {

  2.    ...

  3. }));

既然主构造器没有被调用,那么这段代码一定执行不到,自然就有了获取 firstName 时的空指针了。

那么用 noarg 插件行不行呢?答案自然也是不行的。之前有文章讲到,生成的无参构造器,除了调用了父类的默认无参构造之外,什么事儿都没有做。

当然,有人会说, Unsafe 这个我们管不了,可为什么 noarg 插件生成的默认无参构造也不对类当中定义的这些成员进行初始化呢?

首先,类的设计要求主构造器一定要在实例化的时候调用, noarg 也好 Unsafe 也好,都是不得已做出的妥协,我们不应该为它们找理由让严谨的语言设计做出更多的让步。

其次,有些情况下,需要初始化的成员可能需要得到一个正确的主构造器的参数,如果我们将代码稍作修改:

  1. data class Person(val name: String, val age: Int){

  2.    val lastName = name.split(" ").last()

  3.    val firstName = name.split(" ").first()

  4. }

那么除非主构造器被正确调用,否则 lastNamefirstName 无论如何也不能正确地被初始化。

3. 小结

数据类的初始化往往会突破 Kotlin 语言的安全条件,这让我们的代码处于危险的境地。因此对于需要序列化数据类的情景,大家在编写代码时还是需要多加注意,不要在数据类当中写有特定初始化逻辑的属性,反序列化的场景中,这样的属性无法保证被正确地初始化。显然,数据类就作为数据结构使用就行了,尽量不要越过这条红线做一些其他的事情,以免产生一些奇怪的问题。


欢迎关注微信公众号 Kotlin

0?wx_fmt=jpeg


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值