切换语言为:繁体
作为程序员,你知道表达式和语句的区别吗?

作为程序员,你知道表达式和语句的区别吗?

  • 爱糖宝
  • 2024-11-11
  • 2029
  • 0
  • 0

前言

Kotlin 相比 Java 语言提供了非常多的语法糖,使得日常编码的时候非常灵活,可以借助语法糖非常高效的完成繁琐的工作。但是,如果对这些语法糖的理解不够深入,就会掉进坑里遇到奇奇挂怪的 bug,本篇总结由于表达式和语句差异导致的一个问题。

表达式 && 语句

我们看一个例子,🌰

private val task1 = Runnable { Log.d(TAG, "task1 run() called") }
private val task2 = Runnable { Log.d(TAG, "task2 run() called") }

handler.postDelayed({ task1 }, 1000) // ①
handler.postDelayed(task2, 2000) // ②

以上代码逻辑很简单,就是通过大家熟悉的 Handler 延迟触发一个任务。

首先,这两种写法从语法上讲都是没有问题的,IDE 并没有报错甚至没有任何 warning。 既然看起来没有差异,那么代码执行后 ① 和 ② 这两处的都会有日志输出吗?如果不是的话,是哪一行没有?如果你非常确定的知道答案并且了解原因,那么后面的内容可以不用看了,节省时间可以去干点别的。

实际运行结果是,只有 ② 处会执行 task2 , task1 并不会被执行。

2024-11-04 18:59:11.502 30324-30324 DuDuActivity_TAG                   I  start call
2024-11-04 18:59:13.503 30324-30324 DuDuActivity_TAG                   D  task2 run() called

task1 为什么没有被执行呢?我们再加点日志

        handler.postDelayed({
            Log.i(TAG,"111")
            task1
            Log.i(TAG,"222")
        }, 1000)

运行结果

2024-11-04 19:00:59.201 30801-30801 DuDuActivity_TAG                   I  start call
2024-11-04 19:01:00.204 30801-30801 DuDuActivity_TAG                   I  111
2024-11-04 19:01:00.204 30801-30801 DuDuActivity_TAG                   I  222
2024-11-04 19:01:01.205 30801-30801 DuDuActivity_TAG                   D  task2 run() called

可以看到其实整段代码已经生效了,但是 task1 这个 Runnable 并没有被触发,这是为什么呢?

其实这个问题的根源要从一个最基本的概念出发去解释,编程语言的表达式语句。大学计算机课程的入门教材,其实都会提这两个概念,只不过可能都是一笔带过,毕竟大部分情况下我们不需要区分二者,因为我们在编码的时候 IDE 会自动帮我们纠正错误的用法。

语句和表达式的区别在于,表达式有值,并且能作为另一个表达式的一部分使用;而语句总是包围着它的代码块中的顶层元素,并且没有自己的值。

直接看概念似乎毕竟抽象,我们再举个例子 🌰

fun max(a: Int,b: Int) = if (a > b) a else b

在 Kotlin 中 if/when 都是表达式,是有返回值的。因此,可以直接作为方法的返回值。但是在 Java 中,所有的控制结构都是语句,因此不能像上面这么用, 而只能像下面这么实现。

    private int max(int a, int b) {
        if (a > b) {
            return a;
        }
        return b;
    }

当然,for 循环依然是语句,无论在 Java 还是 Kotlin 中都不能当做表达式使用。

作为程序员,你知道表达式和语句的区别吗?

正是因为 IDE 提供这样的语法提示,使得我们无形中忽略了表达式语句的差异,在 IDE 中把语句和表达式混用之后,根据错误提示在大脑中强行记住了代码不能这些写,得换另一种方式写这样一个概念。久而久之,所有的内容归结为代码,无所谓表达式和语句。

Lambda 表达式中的语句

我们回过头来看问题, 首先从 HandlerpostDelayed 这个方法出发

  • postDelayed

    public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
        return sendMessageDelayed(getPostMessage(r), delayMillis);
    }

首先 postDelayed 方法接受一个 Runnable 类型的变量,因此我们一开始的例子中②处的调用是标准实现,肯定没有问题。

  • Runnable

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

同时 Runnable 这个接口有 @FunctionalInterface 这个注解。FunctionalInterface(函数式接口)是 Java 8 引入的一个概念,它是一个只有一个抽象方法的接口。这个特性使得函数式接口非常适合用作 Lambda 表达式的类型,因为 Lambda 表达式可以作为实现函数式接口的匿名类。

因此,我们可以直接传入一个 Lambda 表达式 {} 。对于 HandlerpostDelayed 方法来说,他需要的只是一个函数式接口,并不一定是 Runnable 这个类型的接口。此时,postDelayed 执行的是这个表达式。

        handler.postDelayed({
            Log.i(TAG,"111")
            task1
            Log.i(TAG,"222")
        }, 1000)

而在这个 Lambda 表达式中,所有的内容都是语句,task1 此时也只是语句,相当于只是声明了一个 Runnable 类型的变量,因此自然不会有结果,想要让 task1 执行,也很简单。

        handler.postDelayed({
            Log.i(TAG,"111")
            task1.run()
            Log.i(TAG,"222")
        }, 1000)

主动调用 task 这个 Runnable 的 run 方法即可。再看一下结果

2024-11-04 19:04:51.450 31297-31297 DuDuActivity_TAG                   I  start call
2024-11-04 19:04:52.453 31297-31297 DuDuActivity_TAG                   I  111
2024-11-04 19:04:52.453 31297-31297 DuDuActivity_TAG                   D  task1 run() called
2024-11-04 19:04:52.453 31297-31297 DuDuActivity_TAG                   I  222
2024-11-04 19:04:53.452 31297-31297 DuDuActivity_TAG                   D  task2 run() called

可以看到这输出已经符合预期了。

因此我们可以确定,在Kotlin 中赋值操作是语句 ,在 Lambda 中只能写语句,因为 Lambda 表达式本身是有返回值的(哪怕是 void,在 Kotlin 中是 Unit),如果其内部还有表达式的话,表达式自身的返回值就没有意义了,或者说会有歧义。因此,从语法上被禁止了。 Lambda 表达式就像一个包含返回值的代码块,让编程语言更灵活了。

Java 中的赋值语句

为了加深理解,我们再来对比一下 Java 语言中类似的情况又会发生什么。

private Runnable task = () -> Log.d("tag", "run() called");

作为程序员,你知道表达式和语句的区别吗?

可以看到在 Java 中这种写法会报错,原因和在 Kotlin 中一样,Lambda 表达式内不允许有表达式,只能是语句,而在 Java 中赋值操作是表达式, 因此天然杜绝了这个隐蔽的 bug。

总结

表达式有返回值,语句没有。同时在 Java 和 Kotlin 中,语句和表达式的有会呈现出很多差异。Kotlin 中更多的内容变成了表达式,这样使得高阶函数,即方法作为参数这样的特性让编码更加灵活,但是如果我们对一些特性的了解不够熟悉的话,就会引入一些非常隐蔽的 Bug。


作者:IAM四十二
链接:https://juejin.cn/post/7436004984951832588

0条评论

您的电子邮件等信息不会被公开,以下所有项均必填

OK! You can skip this field.