上一篇文章介绍了如何对循环语句进行操作,末尾还演示了发现空串时直接继续下一循环,只是在初始化字符串数组时使用了“val poem2Array:Array<String?> = ***”,该表达式不免令人疑惑,为何这里要在String后面加个问号?由此,本文就Kotlin如何判断和处理空值,再做进一步的深入探讨。

以往的开发工作之中,少不了要跟各种异常作斗争,常见的异常种类包括空指针异常NullPointerException、数组越界异常IndexOutOfBoundsException、类型转换异常ClassCastException等等,其中最让人头痛的当数空指针异常,该异常频繁发生却又隐藏很深。调用一个空对象的方法,就会产生空指针异常,可是Java编码的时候编译器不会报错,开发者通常也意识不到问题,只有App运行之时发生闪退,查看崩溃日志才会恍然大悟“原来这里得加上对象非空的判断”。然而,饶是有经验的开发者,尚且摆脱不了如影随形的空指针,何况编程新手呢?问题的症结在于,Java编译器不会检查空值,只能由开发者在代码中增加“if (*** != null)”的判断,但是业务代码里面的方法调用浩如繁星,倘若在每个方法调用之前都加上非空判断,势必大量代码都充满了“if (*** != null)”,这样做的后果不仅降低了代码的可读性,而且给开发者带来不少的额外工作量。

空指针只是狭义上的空值,广义上的空值除了空指针,还包括其它开发者认可的情况。比如说String类型,字符串的长度为0时也可算是空值;如果字符串的内容全部由空格组成,某种意义上也是空值。那么字符串的非空判断,用Java书写的话见下面示例代码:

    if (str!=null && str.length()>0 && str.trim().length()>0) {
......
}

可以看到以上的非空判断语句有点冗长了,因此作为开发者,必须把会被多次调用的代码封装成工具类。既然大家都这么想,Android系统的研发工程师也不例外,所以安卓的SDK已经提供了TextUtils.isEmpty(***)这个公共方法,专门用于校验某个字符串是否为空值。Kotlin的研发人员当然不会放过这点,就像读者在上一篇文章中看到的那样,Kotlin通过isNullOrBlank函数进行空值校验,下面列出Kotlin校验字符串空值的几个方法:
isNullOrEmpty : 为空指针或者字串长度为0时返回true,非空串与可空串均可调用。
isNullOrBlank : 为空指针或者字串长度为0或者全为空格时返回true,非空串与可空串均可调用。
isEmpty : 字串长度为0时返回true,只有非空串可调用。
isBlank : 字串长度为0或者全为空格时返回true,只有非空串可调用。
isNotEmpty : 字串长度大于0时返回true,只有非空串可调用。
isNotBlank : 字串长度大于0且不是全空格串时返回true,只有非空串可调用。

注意到上面的方法有区分非空串与可空串,这是缘于Kotlin引入了空安全的概念,每个类型的对象都分作不可为null和可以为null两种。前面的文章中,正常声明的对象默认都是非空(不可为null),比如下面这个声明字符串变量的代码

    var strNotNull:String = ""

非空对象要么在声明时就赋值,要么在方法调用前赋值;否则未经初始化就调用该对象的方法,Kotlin会像语法错误那样提示这里“Variable *** must be initialized”。至于可以为空的对象,可于声明之时在类型后面加个问号,如同上一篇文章声明可空字符串数组的代码“val poem2Array:Array<String?> = ***”,只声明一个可空字符串对象的代码如下所示:

    var strCanNull:String?

现在有了两个字符串,其中strNotNull为非空串,strCanNull为可空串。按照前面几个字符串空值校验方法的规则,strNotNull允许调用全部六个方法,但strCanNull只允许调用isNullOrEmpty和isNullOrBlank两个方法。因为strCanNull可能为空指针,若去调用一个空指针对象的length方法,毫无疑问会扔出空指针异常,所以Kotlin对可空串增加了编译检查,一旦发现某个可空串调用isEmpty/isBlank/isNotEmpty/isNotBlank,立刻提示此处语法错误“Only *** calls are allowed on a nullable receiver of type String”。

可是上述的几个方法局限于判断字符串是否为空串,如果要求获得字符串的长度,或者调用其它对象类型的方法,仍然要判断空指针。以获取字符串长度为例,下面声明了三个字符串对象,其中strA为非空串,strB和strC都是可空串,不过strB为空而strC实际有值,字符串对象的声明代码如下:

    val strA:String = "非空"
val strB:String? = null
val strC:String? = "可空串"

对于strA,因为它是非空串,所以可直接获取length长度属性。对于strB和strC,必须进行非空判断,否则编译器会提示该行代码存在错误。具体的长度获取代码如下所示:

    var length:Int = 0
btn_length_a.setOnClickListener { length=strA.length; tv_check_result.text="字符串A的长度为$length" }
btn_length_b.setOnClickListener {
//length=strB.length //这种写法是不行的,因为strB可能为空,会扔出空指针异常
length = if (strB!=null) strB.length else -1
tv_check_result.text="字符串B的长度为$length"
}
btn_length_c.setOnClickListener {
//即使strC实际有值,也必须做非空判断,谁叫它号称可空呢?编译器宁可错杀一千、不可放过一个
length = if (strC!=null) strC.length else -1
tv_check_result.text = "字符串C的长度为$length"
}

以上的if/else虽然已经完成非空判断的功能,可是Kotlin仍旧嫌它太啰嗦,中国人把繁体字简化为简体字,外国人也想办法简化编程语言,中外人士果然所见略同。原本直接获取可空串的length属性会扔出空指针异常,那就加个标记,遇到空指针别扔异常,直接返回空指针就好了,至少避免了处理异常的麻烦事。具体的标记代码如下:

    var length_null:Int?
btn_question_dot.setOnClickListener {
//?.表示对象为空时就直接返回null,所以返回值的变量必须被声明为可空类型
length_null = strB?.length
tv_check_result.text = "使用?.得到字符串B的长度为$length_null"
}

从代码中可以看出,这个多出来的标记是个问号,语句“strB?.length”等价于“length_null = if (strB!=null) strB.length else null”。但是,该语句意味着返回值仍然可能为空,如果不想在界面上展示“null”,还得另外判断length_null是否为空;也就是说,这个做法并未实现与原代码完全一致的功能。

没有完成任务,Kotlin当然不会罢休,所以它又引入了一个运算符“?:”,学名叫做“Elvis 操作符”,叫起来有点拗口,读者可以把它当作是Java三元运算符“变量名=条件语句?取值A:取值B”的缩写。引入“?:”的实现代码如下所示:

    btn_question_colon.setOnClickListener {
//?:表示为空时就返回右边的值,即(x!=null)?x.**:y
length = strB?.length?: -1
tv_check_result.text = "使用?:得到字符串B的长度为$length"
}

这样总该完事了吧?然而执拗的Kotlin攻城狮觉得还是啰嗦,因为经常上一行代码就对strB赋值了,所以此时可以百分百保证strB非空,那又何必浪费口舌呢?于是Kotlin另外引入了运算符“!!”,表示甭管那么多了,前方没有地雷,弟兄们赶紧上!下面是“!!”的运用代码例子:

    btn_exclamation_two.setOnClickListener {
strB = "排雷完毕"
length = strB!!.length
tv_check_result.text = "使用?:得到字符串B的长度为$length"
}

既然运算符“!!”强行放弃了非空判断,开发者就得自己注意排雷了。否则的话,一旦出现空指针,App运行时依然会抛出异常。以下的演示代码在运行时会扔出空指针异常,故而增加了异常捕获处理:

    btn_exclamation_two.setOnClickListener {
//!!表示不做非空判断,强制执行后面的表达式,如果对象为空就会扔出空异常
//所以只有在确保为非空时,才能使用!!
try {
//即使返回给可空变量length_null,也会扔出异常
length = strB!!.length
tv_check_result.text = "使用!!得到字符串B的长度为$length"
} catch(e:Exception) {
tv_check_result.text = "发现空指针异常"
}
}

  

总结一下,Kotlin引入了空安全的概念,并在编译时开展对象是否为空的校验。相关的操作符说明概括如下:
1、声明对象实例时,在类型名称后面加问号,表示该对象可以为空;
2、调用对象方法时,在实例名称后面加问号,表示一旦实例为空就返回null;
3、新引入运算符“?:”,一旦实例为空就返回该运算符右边的表达式;
4、新引入运算符“!!”,通知编译器不做非空校验,运行时一旦发现实例为空就扔出异常;

__________________________________________________________________________
本文现已同步发布到微信公众号“老欧说安卓”,打开微信扫一扫下面的二维码,或者直接搜索公众号“老欧说安卓”添加关注,更快更方便地阅读技术干货。

Kotlin入门(8)空值的判断与处理的更多相关文章

  1. Kotlin入门教程——目录索引

    Kotlin是谷歌官方认可的Android开发语言,Android Studio从3.0版本开始就内置了Kotlin,所以未来在App开发中Kotlin取代Java是大势所趋,就像当初Android ...

  2. Kotlin入门(9)函数的基本用法

    上一篇文章介绍了Kotlin新增的空安全机制,控制语句部分可算是讲完了,接下来将连续描述Kotlin如何定义和调用函数,本篇文章先介绍函数的基本用法. 前面几篇文章介绍控制语句之时,在setOnCli ...

  3. Kotlin入门第二课:集合操作

    测试项目Github地址: KotlinForJava 前文传送: Kotlin入门第一课:从对比Java开始 初次尝试用Kotlin实现Android项目 1. 介绍 作为Kotlin入门的第二课, ...

  4. 写给Android开发者的Kotlin入门

    写给Android开发者的Kotlin入门 转 https://www.jianshu.com/p/bb53cba6c8f4 Google在今年的IO大会上宣布,将Android开发的官方语言更换为K ...

  5. Kotlin入门(28)Application单例化

    Application是Android的又一大组件,在App运行过程中,有且仅有一个Application对象贯穿应用的整个生命周期,所以适合在Application中保存应用运行时的全局变量.而开展 ...

  6. Kotlin入门(11)江湖绝技之特殊函数

    上一篇文章介绍了Kotlin对函数的输入参数所做的增强之处,其实函数这块Kotlin还有好些重大改进,集中体现在几类特殊函数,比如泛型函数.内联函数.扩展函数.尾递归函数.高阶函数等等,因此本篇文章就 ...

  7. Kotlin入门(13)类成员的众生相

    上一篇文章介绍了类的简单定义及其构造方式,当时为了方便观察演示结果,在示例代码的构造函数中直接调用toast提示方法,但实际开发是不能这么干的.合理的做法是外部访问类的成员属性或者成员方法,从而获得处 ...

  8. Kotlin入门(14)继承的那些事儿

    上一篇文章介绍了类对成员的声明方式与使用过程,从而初步了解了类的成员及其运用.不过早在<Kotlin入门(12)类的概貌与构造>中,提到MainActivity继承自AppCompatAc ...

  9. Kotlin入门(15)独门秘笈之特殊类

    上一篇文章介绍了Kotlin的几种开放性修饰符,以及如何从基类派生出子类,其中提到了被abstract修饰的抽象类.除了与Java共有的抽象类,Kotlin还新增了好几种特殊类,这些特殊类分别适应不同 ...

随机推荐

  1. MySQL 字符集utf8和utf-8的关系

    目录 什么是字符集(character set) 校对规则(collation) ASCII码 Unicode国际化支持 UTF-8 utf8 utf8与utf8mb4的关系 超集 字符集设置 什么是 ...

  2. linux内核中GNU C __attribute__ 机制的实用

    很多东西,只看看是不行的,要想深入的去了解一个东西,一定要去不断地学习,实践,反思. 说白了就是要去打磨. 在linux中,最近遇到了这样一个定义: int board_usb_init(int in ...

  3. iOS开发笔记-Xcode添加pch文件

    xcode6以后苹果取消了pch文件,需要自己创建pch文件并手动添加引用. 1.新建pch文件 2.项目Build Settings添加引用 最后检查路径是否正确,编译一下查看是否出现问题. 如果出 ...

  4. Python3学习笔记 - day1

    前言 本文不是一篇系统的从零开始学习Python的教程,如果你需要从零开始学习Python,廖雪峰的官方网站中Python教程这部分将是比较好的一种选择,如果你英语比较好,也可以在国外的一些网站上找到 ...

  5. 理解数据库连接池和ThreadLocal实现的事务控制

    我发现 不少人 误解了这两者. csdn上也有人提出过这种疑问: http://bbs.csdn.net/topics/250061733 经过查阅资料和认真分析,我特说明一下这两者概念上的区别. 我 ...

  6. GraphX之Pregel(BSP模型-消息传递机制)学习

    /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreem ...

  7. jquery的contains方法

    <!DOCTYPE html><html> <head> <meta charset="UTF-8"> <title>& ...

  8. Django--models--多表操作

    一 创建模型 实例:我们来假定下面这些概念,字段和关系 作者模型:一个作者有姓名和年龄. 作者详细模型:把作者的详情放到详情表,包含生日,手机号,家庭住址等信息.作者详情模型和作者模型之间是一对一的关 ...

  9. 十余年软件开发经历,经验总结和程序一览(涉及Socket、WPF、vc++、CAD、图像、GIS)

    前言 本文主要介绍我开发的几款软件产品,大概介绍一下功能.可以让读者了解本人的开发能力,所擅长的方面.有相关开发需求可以联系作者. 本人开发软件多年,从事的行业也多种多样:自然接触了不同的业务,开发了 ...

  10. Hashtable,HashMap,TreeMap有什么区别?Vector,ArrayList,LinkedList有什么区别?int和Integer有什么区别?

    接着上篇继续更新. /*请尊重作者劳动成果,转载请标明原文链接:*/ /*https://www.cnblogs.com/jpcflyer/p/10759447.html* / 题目一:Hashtab ...