转载

Kotlin 中的判空操作 Elvis 操作符使用踩坑

Kotlin 作为一门有着所谓 空安全 特性的(年轻)编程语言,有时出于实际业务场景需要还是会把变量声明成可空(Null-able)的,好在由于空安全特性,编译器会强制我们对可空变量进行判空检查(除非你使用了非空断言 !! 强制让编译器闭嘴)。Kotlin 以完全兼容 Java 为设计原则,设计者们该是在设计阶段就预见到了使用者们可能还是需要进行很多判空检查,于是引入了非常简洁优雅的 Elvis 操作符,请看代码:

Java 典型判空操作:

/**
     * <p>控制台打印字符串的长度是多少</>
     * @param str:给定的字符串
     */
    public void askStringLength(String str){
        int i;
        //Java 中我们一般这样判空,或者使用那个三目运算符
        if (str == null){
            i = 0;
        } else{
            i = str.length();
        }

        //或者使用三目运算符
        i = str == null? 0 : str.length();

        System.out.println("The length is " + i);
    }
复制代码

如上,Java 没有所谓空安全特性。你可能需要写大量上面那样的判空模板代码,而且你肯定无法在每一个变量可能为空的地方加上这种防御性代码,毕竟项目里很容易积攒到成千上万的变量,给这么多变量写上防御性代码想想都头大!当然对于很大概率会抛出空指针异常的地方编译器会做高亮提示,但会给出提示的地方放之整个项目而言实在微不足道。我们手动 new 出来的 对象 和 null,作为有着完全不同行为的两种实体居然可以赋值给同一个变量,如此,如果在该判空的地方没做判空,就是一个在运行时可能会抛出 NPE(NullPointerException) 的bug!

Kotlin 重新设计了一套完全不同于 Java 的类型系统,声称可大大较少项目中的 NullPointerExceptio(Kotlin因以完全兼容 Java 为设计原则,因而无法完全杜绝 NPE)。就我本人经验看来,效果还不赖,使用 Koltin 项目中的 NPE 确实少多了。而且 Koltin 中对可能抛出 NPE 的地方做判空检查的方式比之 Java 而言实在优雅,从此远离那些烦人的 if-else,只需借助所谓 Elvis 操作符。

关于 Kotlin 的类型系统与 Java 之异同非本文重点,重点是在 Kotlin 中,如果一个变量是可空的,你在访问此变量时如果不做判空检查是通不过编译的(除非你使用了非空断言 !! 强制让编译器闭嘴)!如此使得我们的代码在运行时抛出 NPE 的概率大大减少!嗯能在编译阶段发现的问题就别留到运行时再发现!下面我们直接看看上面包含判空操作的 Java 代码其等价的 Koltin 代码是怎么样的。

Kotlin 典型判空操作:

/**
     *
     * <p>控制台打印字符串的长度是多少</>
     * @param str:给定的字符串
     */
    fun askStringLength(str: String?) {
        val i: Int
        // Kotlin 中我们一般这样对变量判空
        // ?. 是 Kotlin 中所谓的安全调用操作符
        // 如果 str 非空,就返回 str.length,否则返回 null,表达式(str?.length)返回的类型是 Int?   嗯有可能为 null
        // ?; 就是所谓的 Elvis 操作符
        // 如果 ?: 左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式
        i = str?.length?:0//str.length 这么写是通不过编译的
        // 一路流式操作下来是不是感觉很顺畅,那可比 if-else 舒服多了!注意 Kotlin 中的 Elvis 操作符不是三目操作符,Kotlin 中是不存在三木操作符的

        println("The length is $i")
    }
复制代码

Kotlin 中我们一般这样对变量判空:

i = str?.length?:0
复制代码

?. 是 Kotlin 中所谓的安全调用操作符,如果 str 非空,就返回 str.length,否则返回 null,表达式(str?.length)返回的类型是 Int? , 嗯有可能为 null,?; 就是所谓的 Elvis 操作符,如果 ?: 左侧表达式非空,elvis 操作符就返回其左侧表达式,否则返回右侧表达式(做默认值)。

此时如果我们有了新需求,要求计算一个 List 中所有字符串的长度之和,你很可能会这么写:

/**
     *<p>控制台打印列表中所有字符串长度之和</>
     * @param strings:给定的字符串列表
     */
    fun askStringLength(strings: List<String?>) {
        var i: Int = 0
        strings.forEach { str ->
            i = str?.length?:0 + i //注意这行代码,当然你更可能写成 i += str?.length?:0,为了说明问题先按我写的来
        }
        println("The length is $i")
    }
复制代码

如果真像上面那样写了,那你肯定得不到正确的结果!你可以运行试试!

原因在于 Elvis 操作符的优先级较低,请看下方我从 Kotlin 官网扒来的操作符优先级表:

Precedence Title Symbols
Highest Postfix ++ , -- , . , ?. , ?
Prefix - , + , ++ , -- , ! , label
Type RHS : , as , as?
Multiplicative * , / , %
Additive + , -
Range ..
Infix function simpleIdentifier
Elvis ?:
Named checks in , !in , is , !is
Comparison < , > , <= , >=
Equality == , !==
Conjunction &&
Disjunction ||
Lowest Assignment = , += , -= , *= , /= , %=

注意 Elvis 操作符的位置!

这行代码:

i = str?.length?:0 + i
复制代码

其实等价于

i = str?.length ?: (0 + i)
复制代码

所以想让代码正确运行我们其实应该写成这样:

i = (str?.length?:0) + i
复制代码

使用时千万要注意操作符的优先级!

完。

原文  https://juejin.im/post/5dc52ee4518825592c56680b
正文到此结束
Loading...