用lambda表达式会被骂吗(lambda表达式用法)

http://www.itjxue.com  2023-01-26 16:38  来源:未知  点击次数: 

请高手讲讲lambda表达式到底有什么重要地位

简单来说,编程中提到的 lambda 表达式,通常是在需要一个函数,但是又不想费神去命名一个函数的场合下使用,也就是指匿名函数。这一用法跟所谓 λ 演算(题目说明里的维基链接)的关系,有点像原子弹和质能方程的关系,差别其实还是挺大的。

不谈形式化的 λ 演算,只说有实际用途的匿名函数。先举一个普通的 Python 例子:将一个 list 里的每个元素都平方:

map( lambda x: x*x, [y for y in range(10)] )

这个写法要好过

def sq(x):

return x * xmap(sq, [y for y in range(10)])

,因为后者多定义了一个(污染环境的)函数,尤其如果这个函数只会使用一次的话。而且第一种写法实际上更易读,因为那个映射到列表上的函数具体是要做什么,非常一目了然。如果你仔细观察自己的代码,会发现这种场景其实很常见:你在某处就真的只需要一个能做一件事情的函数而已,连它叫什么名字都无关紧要。Lambda 表达式就可以用来做这件事。

进一步讲,匿名函数本质上就是一个函数,它所抽象出来的东西是一组运算。这是什么意思呢?类比

a = [1, 2, 3]

f = lambda x : x + 1

,你会发现,等号右边的东西完全可以脱离等号左边的东西而存在,等号左边的名字只是右边之实体的标识符。如果你能习惯 [1, 2, 3] 单独存在,那么 lambda x : x + 1 也能单独存在其实也就不难理解了,它的意义就是给「某个数加一」这一运算本身。

现在回头来看 map() 函数,它可以将一个函数映射到一个可枚举类型上面。沿用上面给出的 a 和 f,可以写:

map(f, a)

也就是将函数 f 依次套用在 a 的每一个元素上面,获得结果 [2, 3, 4]。现在用 lambda 表达式来替换 f,就变成:

map( lambda x : x + 1, [1, 2, 3] )

会不会觉得现在很一目了然了?尤其是类比

a = [1, 2, 3]r = []for each in a:

r.append(each+1)

这样的写法时,你会发现自己如果能将「遍历列表,给遇到的每个元素都做某种运算」的过程从一个循环里抽象出来成为一个函数 map,然后用 lambda 表达式将这种运算作为参数传给 map 的话,考虑事情的思维层级会高出一些来,需要顾及的细节也少了一点。Python 之中,类似能用到 lambda 表达式的「高级」函数还有 reduce、filter 等等,很多语言也都有这样的工具(不过这些特性最好不要在 Python 中用太多)。这种能够接受一个函数作为参数的函数叫做「高阶函数」(higher-order function),是来自函数式编程(functional programming)的思想。

为什么要使用lambda表达式?原来如此,涨知识了

先看几段Java8以前经常会遇到的代码:

创建线程并启动

比较数组

给按钮添加单击事件

对于这三段代码,我们已经司空见惯了。

Java复杂冗余的代码实现一直被程序员所诟病,好在随着JVM平台语言Scala的兴起以及函数式编程风格的风靡,让Oracle在Java的第8个系列版本中进行了革命性的变化,推出了一系列函数式编程风格的语法特性,比如Lambda表达式以及Stream。

如果采用Lambda表达式,上面三段代码的实现将会变得极为简洁。

创建线程并启动(采用Lambda版本)

比较数组(采用Lambda版本)

给按钮添加单击事件(采用Lambda版本)

格式:(参数) - 表达式

其中:

一个参数

多个参数

0个参数

表达式块

在Java8中新增加了一个注解: [@FunctionalInterface],函数式接口。

什么是函数式接口呢?它包含了以下特征:

Lambda表达式的本质就是函数式接口的匿名实现。只是把原有的接口实现方式用一种更像函数式编程的语法表示出来。

Java8的java.util.function包已经内置了大量的函数式接口,如下所示:

从中可以看出:

以下是一个综合的例子:

如果觉得这些内置函数式接口还不够用的话,还可以自定义自己的函数式接口,以满足更多的需求。

如果Lambda表达式已经有实现的方法了,则可以用方法引用进行简化。 方法引用的语法如下:

这样前面提到的Lambda表达式:

则可以替换为:

另一个例子:

可以替换为:

注意:方法名后面是不能带参数的! 可以写成System.out::println,但不能写成System.out::println(“hello”)

如果能获取到本实例的this参数,则可以直接用this::实例方法进行访问,对于父类指定方法,用super::实例方法进行访问。

下面是一个例子:

构造器引用和方法引用类似,只不过函数接口返回实例对象或者数组。 构造器引用的语法如下:

举个例子:

其中的labels.stream().map(Button::new)相当于 labels.stream().map(label-new Button(label))

再看个数组类型的构造器引用的例子:

把Stream直接转成了数组类型,这里用Button[]::new来标示数组类型。

先看一段代码:

一个lambda表达式一般由以下三部分组成:

参数和表达式好理解。那自由变量是什么呢? 它就是在lambda表达式中引用的外部变量,比如上例中的text和count变量。

如果熟悉函数式编程的同学会发现,Lambda表达式其实就是”闭包”(closure)。只是Java8并未叫这个名字。 对于自由变量,如果Lambda表达式需要引用,是不允许发生修改的。

比如下面的代码:

先说说为什么要在Java8接口中新增默认方法吧。

比如Collection接口的设计人员针对集合的遍历新增加了一个forEach()方法,用它可以更简洁的遍历集合。 比如:

但如果在接口中新增方法,按照传统的方法,Collection接口的自定义实现类都要实现forEach()方法,这对广大已有实现来说是无法接受的。

于是Java8的设计人员就想出了这个办法:在接口中新增加一个方法类型,叫默认方法,可以提供默认的方法实现,这样实现类如果不实现方法的话,可以默认使用默认方法中的实现。

一个使用例子:

默认方法的加入,可以替代之前经典的接口和抽象类的设计方式,统一把抽象方法和默认实现都放在一个接口中定义。这估计也是从Scala的Trait偷师来的技能吧。

除了默认方法,Java8还支持在接口中定义静态方法以及实现。

比如Java8之前,对于Path接口,一般都会定义一个Paths的工具类,通过静态方法实现接口的辅助方法。

接口中有了静态方法就好办了, 统一在一个接口中搞定!虽然这看上去破坏了接口原有的设计思想。

这样Paths类就没什么意义了~

使用Lambda表达式后可以大幅减少冗余的模板式代码,使把更多注意力放在业务逻辑上,而不是复制一堆重复代码, 除非你在一个用代码行数来衡量工作量的公司,你觉得呢?

lambda表达式,是该多用,还是慎用

慎用,虽然已经出来一段时间了,但是并不是所有人都在用,你的代码肯定不止你一个人看或者维护,如果别人看你的代码可能要去学习,时间也是成本,所以在lambda表达式没普及的时候还是慎用

(责任编辑:IT教学网)

更多

推荐网络工程师文章