专栏首页sickworm4. 变量声明和变量状态设计

4. 变量声明和变量状态设计

本篇文章将会介绍如何通过正确的变量状态设计来达到简化代码逻辑的效果。

本篇并不是针对 Kotlin 的语言特性介绍,但它比语言特性更为重要。上一篇文章讲的是空安全特性,它允许你方便的处理对象可能为空的情况。但他价值更大的另一面在于,Kotlin 可以声明不可能为空的对象

1. 非空类型

对象不可能为空意味着程序复杂度的降低。而且这不是一般的降低,因为我们开发过程很多时候都是在处理“这个变量可能为空”的情况。在 Java 的环境里,我们出于对调用的 SDK 的不信任,总是要去判断以下是否为空,以保平安,这样处理的代价就是,增加了大量的异常分支代码。如果一个变量他永远都不可能为空,那其实是一件很快乐的事!一个对象可能的状态减少了,程序逻辑会变得更简单清晰,代码的可维护性会大大的提高。我们应该尽量将一个变量声明为非空类型。

Java 提供了 @NonNull 和 @Nullable 注解来满足对象状态的空设计。但由于默认只会产生警告级别的提示(相信我,很多程序员不看 warning),以及使用的繁琐,它最终落得和 final 一样的使用频率。

你很可能会担心非空类型会带来内存泄漏。因为在 Java 很多释放操作都会将引用的变量设置为空,这是个很常见的防止内存泄漏的办法。但代价是将程序状态复杂化。我们确实应该慎重考虑一个变量是否可以一直被持有,但大部分情况我们是可以不用担心的。如 Android 开发基本只要考虑 Activity 是否间接被单例这样生命周期过长的对象持有即可。我还依稀记得刚学 Android 的时候,有些网上教程还会教你在 onDestroy 的时候将 onClickListner 设置为 null 防止内存泄漏。。

2. lateinit

说到尽量声明为非空类型,有人就会提出质疑了:非空类型说来简单,但部分依赖外部调用完成初始化的变量,无法声明为非空类型啊?Activity 的初始化,就是通过 onCreateView 回调初始化的,各种 UI 对象只能在 onCreate 回调的时候被赋值。

针对这种情况,可以使用 Kotlin 的 lateinit 关键字。lateinit 人如其名,它表示这个对象会在稍后被初始化。它还有两条限制:

  1. 无法用 val 修饰,只能用 var 修饰;
  2. 必须为非空类型。

1 很好理解,val 意义是声明后无法再被重新赋值,就和 final 一样。而 lateinit 变量要在稍后才被赋值,所以必须是 var。var 也意味着 lateinit 变量可以被多次赋值,可被多次赋值可能是你想要的,也有可能是你不想要的。

2 的话,设想一下,如果是可空类型,也没必要用 lateinit 了,直接初始化为 null 即可。所以 2 也是合理的。

如果一个变量被声明为 lateinit,你可以不用在声明时初始化它,在任意地方把它当作非空类型直接使用。注意了,此时如果你在初始化这个变量前就使用了该变量,则会丢出一个 RuntimeException:

UninitializedPropertyAccessException: lateinit property has not been initialized

意思就是你还没初始化这个变量就使用它了。所以使用 lateinit 关键字,就需要你自己保证调用顺序,保证调用时变量已经被初始化,Kotlin 不再帮你把关了。这看起来像是一个把 Kotlin 空安全废掉,退化为原来 Java 的无空检查的行为。这样就很没意思了,但其实不是这样,lateinit 有他特有的表意,即:这个变量在稍后会被初始化,且以后都不再为空。以后不再为空即是他和可空变量的区别,从状态复杂度来看,lateinit 变量是介于非空变量和可空变量之间的。

使用 lateinit 是一个有风险的事情,因为非空的条件变复杂了(初始化后才是非空)。如果你不能保证所有调用都在赋值后发生,则不应使用它。但对于 Activity 的 onCreate 这种简单的场景,还是建议使用 lateinit 的。但需要注意一点:

如果 Activity 在 onCreate 的时候初始化失败了,你需要弹窗或直接 finish 的时候,此时你的 lateinit 变量可能没有被赋值,而 Activity 仍会执行 onStart onResume onDestroy 这些回调。这种情况就是“没法保证调用前变量已经初始化”的情况了。

这个时候你可以选择将变量声明为可空类型。也可以用 lateinit 变量专有的判断方法::xxx.isInitialized在关键路径进行判断,比如 Activity onCreate finish 掉的话,关键路径就只剩 onDestroy了(Fragmet 还有 onCreateView 和 onViewCreated)。但相比这两种办法,我更建议你思考,这样复杂的情景是不是我想要的,设计是否能够简化?因为正确设计的程序的状态应该是简单清晰的。

3. 空对象模式(Null Object Pattern)

其实相对于 lateinit,我更喜欢空对象这个设计模式。它没有 lateinit 引入的风险,是一种更简单的状态。空对象就是拥有这个类默认实现的对象。对于数据类来说,它的空对象可能所有成员变量都是0,false,长度为0的字符串;对于带方法的类来说,它的空对象可能是所有方法都是空的,可以调用但没有任何效果。这样一个空对象,它可以帮你代替 null,临时顶替正常实现,直到被重新赋值。同样是初始化异常,lateinit 可能会崩溃,而空对象最多是表现异常。

可参考:https://en.wikipedia.org/wiki/Null_object_pattern

4. final

除了 Kotlin 的非空类型/可空类型,val/var(即 Java 的 final 关键字)也是减少变量状态的利器。而且它比非空类型更彻底,非空类型只是不允许这个变量变为 null,val 直接不允许变量重新被赋值!声明为 val 的变量状态可能性更少,并发竞争的问题都没有了。

变量状态设计原则

经过上面的变量状态介绍,我们按照变量状态从简单到复杂的顺序,可以得到一个变量状态声明的优先级:

  1. 声明为 val 变量,无法满足再考虑 var
  2. 声明为非空变量
  3. 无法满足声明时赋值,优先考虑赋值为空对象
  4. 无法满足空对象,看看是否可以用 lateinit
  5. 声明为可空变量

版权所有,转载请注明出处: https://sickworm.com/?p=1784

共享此文章:

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 深入理解计算机系统(第三版)/ CSAPP 杂谈,第9章:虚拟内存

    sickworm
  • 《敏捷软件开发:原则、模式与实践》笔记(4)

    一个模型,如果孤立的来看,并不具有真正意义上的有效性。模型的有效性只能通过它的客户程序来体现。能解决问题的模型才是好模型。

    sickworm
  • 《计算机网络:自顶向下方法》笔记(2):应用层

    运输层提供了 TCP 和 UDP 两种运输服务。TCP 是面向连接的,并提供了可靠的数据传输服务。UDP 不是面向连接的,切不提供可靠数据传输服务。

    sickworm
  • java OJ题目判断输入结束(与C语言的EOF结束等价)

    /* * java 作Oj题目是会有输入若干数据的情况,不好判断输入结束符, * 类似于C语言中的EOF符号 * 在这里提供了一种方法 * */

    用户3030674
  • 区分函数 ROW_NUMBER(), RANK(), and DENSE_RANK()

    今天给大家推送第一篇SQL文章《辨析函数 ROW_NUMBER(), RANK(), and DENSE_RANK() 》,接下来所讨论的全部内容都是基于ORA...

    用户6021891
  • JAVA工作三年面试(二)

    这里讲述下第二家公司的面试,这是一家大型互联网公司,简称W,一般像博主这样的传统行业去跳到这种公司简直是要跪舔的节奏,所以从一开始就带着一份敬仰之情去面试。由于...

    小柒2012
  • 性能基础之大型网站架构演化(整理篇)

    本文整理自《大型网站技术架构 核心原理与案例分析》一书,这本书应该算一本很强的内功秘籍,虽然没有实战教学,但是基础理论扎实了是很重要的,书中观点明确,设计的问题...

    高楼Zee
  • [享学Netflix] 三十五、Hystrix执行过程集大成者:AbstractCommand详解

    Hystrix的源码因为是基于RxJava来书写的,一方面是很多小伙伴对RxJava并不熟悉,另一方面是基于观察者模式实现的代码绕来绕去就是不好理解,所以总的来...

    BAT的乌托邦
  • ubuntu下apache新建虚拟主机

    最近发现在一个服务器上面布了一些项目,如果不用虚拟主机,用链接跳进去的话,有时候路径会出错,而自己在配置虚拟主机的时候又出现了一些问题,看似简单的东西,却花费了...

    lin_zone
  • MySQL数据库优化那些事 转

    这篇博文主要谈MySQL数据库发展周期中所面临的问题及优化方案,暂且抛开前端应用不说,大致分为以下五个阶段:

    阿dai学长

扫码关注云+社区

领取腾讯云代金券