写点什么

Kotlin 中逻辑运算符操作分析

用户头像
maijun
关注
发布于: 刚刚

在进行 Kotlin DSL 开发时,用到了 Kotlin DSL 的 逻辑运算符(and,or)等,本来以为 Kotlin DSL 中逻辑运算符在短路策略中,和 Java 及 普通 Kotlin 代码一致,但是发现效果并不如此。本文将介绍 Kotlin 中逻辑运算符进行简单介绍,并简单剖析实现机制。本文将主要采用代码示例分析。

1. 问题引入

1.1 Kotlin 中逻辑运算符 &&处理

我们首先看下面的代码:

data class Person(val name: String, val friends: List<Person> = listOf())
fun main() { val mike = Person("mike") val john = Person("john", listOf(mike)) val lili = Person("lili", listOf(mike, john))
println(lili.friends.isNotEmpty() && lili.friends[0].name == "mike") println(mike.friends.isNotEmpty() && mike.friends[0].name == "john")}
复制代码

如代码中所示:

mike 只有一个名字,并没有一个朋友,friends 是一个空的 List。虽然如此,在第 9 行,进行判断时,虽然我们有 mike.friends[0].name 中,取 mike 的第一个朋友的操作,但是因为 短路操作 的处理,前面 mike.friends.isNotEmpty() 已经返回了 false,所以,不会执行到后面的语句,后面的语句是安全的。

上面的代码运行,会有如下输出:

truefalse
复制代码

1.2 Kotlin 中逻辑运算符 and 处理

但是,如果我们考虑 Kotlin DSL 操作,则会有不同的现象。看下面的代码:

package zmj.test.kotlin.dsl
data class Person(val name: String, val friends: List<Person> = listOf())
infix fun Person.match(block: Person.() -> Boolean): Boolean { return block(this)}
fun main() { val mike = Person("mike") val john = Person("john", listOf(mike)) val lili = Person("lili", listOf(mike, john))
println(lili match { (friends.isNotEmpty()) and (friends[0].name == "mike") }) println(mike match { (friends.isNotEmpty()) and (friends[0].name == "john") })}
复制代码

如上代码,我们在第 3-5 行,定义了一个 中缀函数,对 Person 进行校验,看 Person 中字段是否能够满足特定条件。

此时在第 13 行,我们的写法还是和 1.1 中示例一样,首先对 friends 进行校验,如果非空,再校验 friends 的第一个元素的名字是否为 john。

但是,此时执行代码,运行结果如下:

trueException in thread "main" java.lang.IndexOutOfBoundsException: Empty list doesn't contain element at index 0.	at kotlin.collections.EmptyList.get(Collections.kt:36)	at kotlin.collections.EmptyList.get(Collections.kt:24)	at zmj.test.kotlin.dsl.MainKt$main$2.invoke(main.kt:15)	at zmj.test.kotlin.dsl.MainKt$main$2.invoke(main.kt)	at zmj.test.kotlin.dsl.MainKt.match(main.kt:6)	at zmj.test.kotlin.dsl.MainKt.main(main.kt:15)	at zmj.test.kotlin.dsl.MainKt.main(main.kt)
Process finished with exit code 1
复制代码

如上,有个异常 IndexOutOfBoundsException,在代码第 15 行。但是,我们在取 friends 的第一个 元素之前,有判空操作,为什么还是会有下标越界异常呢?

1.3 and 和 && 区别

其实,最开始,我以为是 Kotlin 中,普通的操作 和 DSL 操作有区别,因为,我们习惯使用 and 和 or 作为 DSL 操作,更符合自然语言特征,而 && 和 || 更像是代码的操作符。不过根据 Kotlin 代码注释,发现有很大区别,如下:


/** * Performs a logical `and` operation between this Boolean and the [other] one. Unlike the `&&` operator, * this function does not perform short-circuit evaluation. Both `this` and [other] will always be evaluated. */ public infix fun and(other: Boolean): Boolean
/** * Performs a logical `or` operation between this Boolean and the [other] one. Unlike the `||` operator, * this function does not perform short-circuit evaluation. Both `this` and [other] will always be evaluated. */ public infix fun or(other: Boolean): Boolean
复制代码

上面代码,是 Kotlin.Boolean 中抠出来的。上面提示:and 是逻辑与操作,但是,and 并不会执行短路操作,而是会进行全部比较操作。因此,and 和 &&, or 和 ||,并不仅仅 Kotlin 的一种 alias 的操作符,还是有很大区别的。

那么,and 和 or 是如何实现的呢?下一部分还是以 and 和 && 进行分析。

2. Kotlin 逻辑运算符 and 实现机制

2.1 Kotlin 逻辑运算符 and 编译结果分析

我们都知道,Kotlin 的代码,都是编译成 Java 的字节码,然后在 Java 虚拟机中运行的。因此,分析 Kotlin and 操作的处理,从 Kotlin 代码编译后的字节码上面分析非常恰当。

上面的 Kotlin 代码,生成的 class 文件有四个:

Mode                 LastWriteTime         Length Name----                 -------------         ------ -----a----         2021/10/4     21:59           1767 MainKt$main$1.class-a----         2021/10/4     21:59           1767 MainKt$main$2.class-a----         2021/10/4     21:59           2235 MainKt.class-a----         2021/10/4     21:59           3241 Person.class
复制代码

我们反编译代码(我使用的是 jd-gui 工具),然后查找对应于上面第 15 行的反编译的代码信息,内容如下:

    public final boolean invoke(@NotNull Person $receiver) {      Intrinsics.checkNotNullParameter($receiver, "$receiver");      List<Person> list = $receiver.getFriends();      boolean bool = false;      return (!list.isEmpty()) & Intrinsics.areEqual(((Person)$receiver.getFriends().get(0)).getName(), "john");    }
复制代码

从上面反汇编出来的结果,Kotlin 中的 and 运算,最终处理后的操作是 Java 中的 & 操作。

2.2 Java 中 &&和 &的区别和联系

(1) 相同点:

“&&”和 “&”都可以用作逻辑与的运算符 ,当运算符两边的表达式的结果都为 true 时,整个运算结果才为 true,否则,只要有一方为 false,则结果为 false。

(2) 不同点:

  • “&&”还具有短路的功能,即如果第一个表达式为 false,则不再计算第二个表达式。

  • “&”还可以用作位运算符。

3. 总结

本文介绍分析了 Kotlin 中,and 和 &&,or 和||等,不同的操作符的区别和联系,并剖析了 and 的实现方式。

发布于: 刚刚阅读数: 3
用户头像

maijun

关注

还未添加个人签名 2019.09.20 加入

关注基于源码的静态代码分析,缺陷模式识别,Java白盒审计等

评论

发布
暂无评论
Kotlin中逻辑运算符操作分析