前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小心,在数据类当中用 Lazy 要谨慎!

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

作者头像
bennyhuo
发布2020-02-20 13:19:59
1K0
发布2020-02-20 13:19:59
举报
文章被收录于专栏:Bennyhuo

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

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

代码语言:javascript
复制
data class Person(val name: String, val age: Int){
    val lastName: String by lazy { 
        name.split(" ").last()
    }

    val firstName: String by lazy {
        name.split(" ").first()
    }
}

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

代码语言:javascript
复制
val trump = Person("Donald Trump", 71)
println(trump.firstName)

输出的就是:

代码语言:javascript
复制
Donald

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

代码语言:javascript
复制
val trump = Gson().fromJson(json, Person::class.java)
println(trump.firstName)

这个 json 长这样:

代码语言:javascript
复制
{
    "name": "Donald Trump",
    "age": 71
}

那么结果呢?

代码语言:javascript
复制
Exception in thread "main" java.lang.NullPointerException
    at com.bennyhuo.kotlin.ext4nulls.Person.getFirstName(main.kt)
    at com.bennyhuo.kotlin.ext4nulls.MainKt.main(main.kt:12)

2. 敢问坑来自何方?

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

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

代码语言:javascript
复制
public final String getFirstName() {
  Lazy var1 = this.firstName$delegate;
  KProperty var3 = $$delegatedProperties[1];
  return (String)var1.getValue();
}

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

代码语言:javascript
复制
this.firstName$delegate = LazyKt.lazy((Function0)(new Function0() {
    ...
}));

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

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

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

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

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

代码语言:javascript
复制
data class Person(val name: String, val age: Int){
    val lastName = name.split(" ").last()
    val firstName = name.split(" ").first()
}

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

3. 小结

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


本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2017-12-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Kotlin 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1.数据类中使用 lazy 遇到坑
  • 2. 敢问坑来自何方?
  • 3. 小结
相关产品与服务
文件存储
文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档