相等性
Kotlin 中有两种类型的相等性:
1. 引用相等(两个引用指向同⼀对象)
2. 结构相等(用 equals() 检查)
引⽤相等
引用相等由 ===(以及其否定形式 !== )操作判断。a === b 当且仅当 a 和 b 指向同⼀个对象时求值为 true。
结构相等
结构相等由 ==(以及其否定形式 != )操作判断。按照惯例,像 a == b 这样的表达式会翻译成
a?.equals(b) ?: (b === null)
也就是说如果 a 不是 null 则调用 equals(Any?) 函数,否则(即 a 是 null )检查 b 是否与 null 引用相等。
请注意,当与 null 显式比较时完全没必要优化你的代码:a == null 会被自动转换为 a=== null 。
浮点数相等性
当相等性检测的两个操作数都是静态已知的(可空或非空的)Float 或 Double 类型时,该检测遵循 IEEE 754 浮点数运算标准。否则会使用不符合该标准的结构相等性检测,这会导致 NaN 等于其自身,而 -0.0 不等于 0.0 。
讨论的浮点数操作如下:
1. 相等性检测:a == b 与 a != b
2. 比较操作符:a < b 、a > b 、a <= b 、a >= b
3. 区间实例以及区间检测:a..b 、x in a..b 、x !in a..b
当其中的操作数 a 与 b 都是静态已知的 Float 或 Double 或者它们对应的可空类型(声明为该类型,或者推断为该类型,或者智能类型转换的结果是该类型),两数字所形成的操作或者区间遵循 IEEE 754 浮点运算标准。
然而,为了支持泛型场景并提供全序⽀持,当这些操作符并非静态类型为浮点数(例如是 Any 、Comparable<……> 、类型参数)时,这些操作使用为Float 与 Double 实现的不符合标准的 equals 与 compareTo ,这会出现:
1. 认为 NaN 与其自身相等
2. 认为 NaN 比包括正无穷大( POSITIVE_INFINITY )在内的任何其他元素都大
3. 认为 -0.0 小于 0.0(译注:这条编译执行可印证,但在 REPL 中不应验,其他两条在 REPL 中也可印证)
空安全
可空类型与非空类型
Kotlin 的类型系统旨在消除来自代码空引用的危险,也称为《⼗亿美元的错误》。
许多编程语言(包括 Java)中最常见的陷阱之⼀是访问空引用的成员,导致空引用异常。在 Java 中,这等同于 NullPointerException 或简称 NPE 。
Kotlin 的类型系统旨在从我们的代码中消除 NullPointerException 。NPE 的唯⼀可能的原因可能是显式调用 throw NullPointerException()。
1. 使用了下文描述的 !! 操作符
2. 外部 Java 代码导致的
3. 对于初始化,有⼀些数据不⼀致(如⼀个未初始化的 this 用于构造函数的某个地方)
在 Kotlin 中,类型系统区分⼀个引用可以容纳 null(可空引用)还是不能容纳(非空引用)。例如,String 类型的常规变量不能容纳 null:
var a: String = "abc"
a = null // 编译错误
如果要允许为空,我们可以声明⼀个变量为可空字符串,写作 String? :
var b: String? = "abc"
b = null // ok,因为引用b是可空的
fun nullTest(){
var s :String? = "null"
s = null
println(s.length) //这里就会爆编译的错误
}
在条件中检查null
检查很智能的
fun ifNullTest(b : String){
if (b!=null &&b.length > 0){
println("is not empty String")
}else{
println("empty String")
}
}
请注意,这只适用于 b 是不可变的情况(即在检查和使用之间没有修改过的局部变量 ,或者不可覆盖并且有幕后字段的 val 成员),因为否则可能会发生在检查之后 b 又变为 null 的情况。
安全的调用
你的第⼆个选择是安全调用操作符,写作 ?. :
fun nullTest(b : String):Int?{
return b?.length //如果 b ⾮空,就返回 b.length ,否则返回 null,这个表达式的类型是 Int?
/*安全调用在链式调⽤中很有用。例如,如果⼀个员⼯ Bob 可能会(或者不会)分配给⼀个部门,
并且可能有另外⼀个员⼯是该部门的负责⼈,那么获取 Bob所在部⻔负责⼈(如果有的话)的名字,
我们写作*/
// bob?.department?.head?.name
// 如果任意⼀个属性(环节)为空,这个链式调⽤就会返回 null
}
如果要只对非空值执行某个操作,安全调用操作符可以与 let ⼀起使用
fun nullTest(){
val listWithNulls: List<String?> = listOf("A", null,"23",null)
for (item in listWithNulls) {
item?.let { println(it) } // 输出 A 并忽略 null
}
}
fun main(args: Array<String>) {
nullTest()
}
Elvis 操作符
当我们有⼀个可空的引用 r 时,我们可以说“如果 r 非空,我使用它;否则使用某个非空的值 x ”:
val l: Int = if (b != null) b.length else -1
除了使用完成整的ifelse表达式,还可以使用 Elvis 操作符表达,写作 ?:
fun nullTest(b:String):Int{
return b?.length ?: -1 //如果 ?: 左侧表达式⾮空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。
//请注意,当且仅当左侧为空时,
// 才会对右侧表达式求值
//上一个函数为了返回安全的值,返回值声明要是Int?类型
}
如果 ?: 左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式。请注意,当且仅当左侧为空时,才会对右侧表达式求值。
请注意,因为 throw 和 return 在 Kotlin 中都是表达式,所以它们也可以用在 elvis 操作符右侧。这可能会非常方便,例如,检查函数参数:
fun Test(node:Node):String?{
val parent = node.parentElement ?: return null
val name :String = node.localName ?: throw IllegalArgumentException("name expected")
return null
}
!! 操作符
第三种选择是为 NPE 爱好者准备的。我们可以写 b!! ,这会返回⼀个非空的 b 值(例如:在我们例⼦中的 String )或者如果 b 为空,就会抛出⼀个NPE 异常。
fun nullTest(b : String?):Int?{
return b!!.length//因此,如果你想要⼀个 NPE,你可以得到它,但是你必须显式要求它,否则它不会不期⽽⾄。
}
fun main(args: Array<String>) {
nullTest(null)
}
安全的类型转换
如果对象不是目标类型,那么常规类型转换可能会导致 ClassCastException 。另⼀个选择是使用安全的类型转换,如果尝试转换不成功则返回 null 如下:会输出null
fun main(args: Array<String>) {
var a = 1
var b :String? = a as? String
println(b) //输出null
}
可空类型的集合
如果你有⼀个可空类型元素的集合,并且想要过滤非空元素,你可以使用 filterNotNull 来实现:
fun main(args: Array<String>) {
val nullableList = listOf<Int?>(1,2,4,null,5)
val list1 = nullableList.filterNotNull()
println(list1)
}
异常
异常类
Kotlin 中所有异常类都是 Throwable 类的子孙类。每个异常都有消息、堆栈回溯信息和可选的原因。
使用 throw-表达式来抛出异常
小编了解到
kotlin 中方法不能声明抛出的异常,不像java一样方法签名上可以声明抛出多个不同类型的异常(官方文档中没有写),
kotlin 只能返回Nothing类型
fun exceptionTest(b: String?){
try {
b?.length?:throw MyException("b cant be null")
}catch (e : MyException){
println("My exception ${e.message}")
}catch (ex : NullPointerException){
println("${ex.message}")
}
}
fun main(args: Array<String>) {
exceptionTest(null)
}
try
是⼀个表达式,即它可以有⼀个返回值
val a: Int? = try { parseInt(input) } catch (e: NumberFormatException) { null }
try-
表达式的返回值是 try
块中的最后⼀个表达式或者是(所有)catch
块中的最后⼀个表达式。finally
块中的内容不会影响表达式的结果。
受检的异常
Kotlin 没有受检的异常。这其中有很多原因,但我们会提供⼀个简单的例⼦。
以下是 JDK 中 StringBuilder 类实现的⼀个⽰例接⼝
Appendable append(CharSequence csq) throws IOException;
这个签名是什么意思?它是说,每次我追加⼀个字符串到⼀些东西(⼀个 StringBuilder
、某种日志、⼀个控制台等)上时我就必须捕获那些IOException
。为什么?因为它可能正在执行 IO 操作( Writer 也实现了 Appendable )…… 所以它导致这种代码随处可见的出现:
try {
log.append(message)
}c
atch (IOException e) {
// 必须要安全
}
这并不好,参⻅《Effective Java》第 65 条:不要忽略异常
通过⼀些小程序测试得出的结论是异常规范会同时提搞开发者的生产力和代码质量,但是大型软件项目的经验表明⼀个不同的结论⸺生产力降低、代码质量很少或没有提高。
Nothing 类型
在 Kotlin 中 throw 是表达式,所以你可以使用它(比如)作为 Elvis 表达式的⼀部分:
val s = person.name ?: throw IllegalArgumentException("Name required")
throw 表达式的类型是特殊类型 Nothing 。该类型没有值,而是用于标记永远不能达到的代码位置。在你自己的代码中,你可以使用 Nothing 来标记⼀个永远不会返回的函数:
fun fail(message: String): Nothing {
throw IllegalArgumentException(message)
}
fun fail(message : String):Nothing{
throw IllegalArgumentException(message)
}
//当你调⽤该函数时,编译器会知道执⾏不会超出该调⽤
fun main(args: Array<String>) {
val s = person.name ?: fail("Name required")
println(s) // 在此已知“s”已初始化
}
可能会遇到这个类型的另⼀种情况是类型推断。这个类型的可空变体 Nothing? 有⼀个可能的值是 null 。如果用 null 来初始化⼀个要推断类型的值,而又没有其他信息可用于确定更具体的类型时,编译器会推断出 Nothing? 类型:
fun main(args: Array<String>) {
val x = null //“x”具有类型 `Nothing?`
val list = listOf(null) //“l”具有类型 `List<Nothing?>
println(list)
}