Lambda Function
Definition
Lambda函数也被称为匿名(没有名称)函数,它直接接受参数的数量以及使用该参数执行的条件或操作,该参数以冒号分隔,并返回最终结果。为了在大型代码库上编写代码时执行一项小任务,或者在函数中执行一项小任务,便在正常过程中使用lambda函数。
For C++,
C++ 11 中的 Lambda 表达式用于定义并创建匿名的函数对象,以简化编程工作
Lambda 语法形式:
[capture](parameters) mutable ->return-type{statement}
[函数对象参数] (操作符重载函数参数) mutable 或 exception 声明 -> 返回值类型 {函数体}。可以看到,Lambda 主要分为五个部分:[函数对象参数]、(操作符重载函数参数)、mutable 或 exception 声明、-> 返回值类型、{函数体}.
(1) [capture][函数对象参数]:标识一个 Lambda 表达式的开始,这部分必须存在,不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的。函数对象参数只能使用那些到定义 Lambda 为止时 Lambda 所在作用范围内可见的局部变量(包括 Lambda 所在类的 this,通俗:主函数的局部变量等)。函数对象参数有以下形式:
[]。没有任何函数对象参数。
[=]。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是值传递方式(相当于编译器自动为我们按值传递了所有局部变量)。
[&]。函数体内可以使用 Lambda 所在范围内所有可见的局部变量(包括 Lambda 所在类的 this),并且是引用传递方式(相当于是编译器自动为我们按引用传递了所有局部变量)。
[this]。函数体内可以使用 Lambda 所在类中的成员变量。
[a]。将 a 按值进行传递。按值进行传递时,函数体内不能修改传递进来的 a 的拷贝,因为默认情况下函数是 const 的,要修改传递进来的拷贝,可以添加 mutable 修饰符。
[&a]。将 a 按引用进行传递。
[a,&b]。将 a 按值传递,b 按引用进行传递。
[=,&a,&b]。除 a 和 b 按引用进行传递外,其他参数都按值进行传递。
[&,a,b]。除 a 和 b 按值进行传递外,其他参数都按引用进行传递。
注意:值传递,在lambda函数定义时就确定了,不会随lambda引用改变。
(2) (parameters)(操作符重载函数参数):标识重载的 () 操作符的参数,没有参数时,这部分可以省略。参数可以通过按值(如: (a, b))和按引用 (如: (&a, &b)) 两种方式进行传递。
(3) mutable 或 exception 声明:这部分可以省略。按值传递函数对象参数时,加上 mutable 修饰符后,可以修改传递进来的拷贝(注意是能修改拷贝,而不是值本身)。exception 声明用于指定函数抛出的异常,如抛出整数类型的异常,可以使用 throw(int)。
(4) ->return-type-> 返回值类型:标识函数返回值的类型,当返回值为 void,或者函数体中只有一处 return 的地方(此时编译器可以自动推断出返回值类型)时,这部分可以省略。
(5) {statement}{函数体}:标识函数的实现,这部分不能省略,但函数体可以为空。
For Java,
匿名内部类会创建 class 文件,lambda 只能用于单个抽象方法的接口
java8 的 lambda 引入了两个核心思想:
方法引用
行为参数化 把一个方法作为参数传递给另一个方法
所以很多时候为了明确的标注出你是一个函数式接口,往往会在接口上面增加一行注释@functionalInterface。但是,默认方法和静态方法不会破坏函数式接口的定义。
之所以在JDK1.8之后提供有默认和静态方法,也都是为函数式开发做准备。
Lambda表达式的几种格式
方法没有参数: () -> {};
方法有参数::(参数,…,参数) -> {};
使用Lambda表达式(无参)
要创建接口 IMessage 的实现类,如果该类只是使用一次,我们可以使用匿名内部类的方式,但是匿名内部类写起来很麻烦。而 IMessage 接口中,只有一个抽象方法,是一个函数式接口,那么我们就可以使用 lambda 来代替匿名内部类。lambda 体就是接口的实现。
@FunctionalInterface
interface IMessage {
public void send();
}
public class JavaDemo {
public static void main(String[] args) {
IMessage i = () -> System.out.println("www.baidu.com");
i.send();
}
}
注:抽象方法如果没有参数,则 lambda 表达式不能省略 ();
使用Lambda表达式(有参)
IMath 接口中只有一个抽象方法,该方法有返回值,且有两个参数。可以使用 lambda 进行简化。
@FunctionalInterface
interface IMath {
public int add(int x, int y);
}
public class JavaDemo {
public static void main(String[] args) {
// t1,t2是形参名,随便取,但是个数必须匹配形参
IMath math = (t1, t2) -> {
return t1 + t2;
};
System.out.println(math.add(20, 30));
}
}
以上的表达式之中你会发现只有一行语句“ return t1 + t2;”,这时候可以进一步简化。
使用Lambda表达式简化(再度简化Lambda表达式,把return语句也省略)
@FunctionalInterface
interface IMath{
public int add(int x , int y);
}
public class Demo01 {
IMath math = (n1,n2)-> n1 + n2;
System.out.println(math.add(10,20));
}
}
Conclusion
lambda 表达式形式为 ()->{},-> 左边是抽象方法的形参列表, -> 是抽象方法的实现体。
lambda 方法如果没有参数或有两个及以上的参数,则 小括号不能省略。
lambda 方法如果只有一个参数,则小括号可以省略。
lambda 方法体如果只有一行语句,则 大括号和return都可省略。
省略了大括号,则必须省略 return,省略了 return ,则必须省略 {},这俩要么成对出现,要么都不出现。
lambda 的本质就是函数式接口的一个实现类。
eg:
/**
* 通过 lambda 表达式优化代码
*/
@Test
public void t2(){
int[] a = {1,2,3,4};
ProcessArray processArray = new ProcessArray();
processArray.process(a, target -> {
int sum = 0;
for (int i : target) {
sum += i;
}
log.info("数组和为{}", sum);
});
}
For Python,
lambda语法形式:
lambda argument_list: expression
其中,lambda是Python预留的关键字,argument_list和expression由用户自定义。具体介绍如下。
- 这里的argument_list是参数列表,它的结构与Python中函数(function)的参数列表是一样的。具体来说,argument_list可以有非常多的形式。例如:
a, b
a=1, b=2
*args
**kwargs
a, b=1, *args
空
…
2. 这里的expression是一个关于参数的表达式。表达式中出现的参数需要在argument_list中有定义,并且表达式只能是单行的。以下都是合法的表达式:
1
None
a + b
sum(a)
1 if a >10 else 0
…
3. 这里的lambda argument_list: expression表示的是一个函数。这个函数叫做lambda函数。
Characteristic
lambda函数有如下特性:
lambda函数是匿名的:所谓匿名函数,通俗地说就是没有名字的函数。lambda函数没有名字。
lambda函数有输入和输出:输入是传入到参数列表argument_list的值,输出是根据表达式expression计算得到的值。
lambda函数一般功能简单:单行expression决定了lambda函数不可能完成复杂的逻辑,只能完成非常简单的功能。由于其实现的功能一目了然,甚至不需要专门的名字来说明。
下面是一些lambda函数示例:
lambda x, y: x*y;函数输入是x和y,输出是它们的积x*y
lambda:None;函数没有输入参数,输出是None
lambda *args: sum(args); 输入是任意个数的参数,输出是它们的和(隐性要求是输入参数必须能够进行加法运算)
lambda **kwargs: 1;输入是任意键值对参数,输出是1
Cases
由于lambda语法是固定的,其本质上只有一种用法,那就是定义一个lambda函数。在实际中,根据这个lambda函数应用场景的不同,可以将lambda函数的用法扩展为以下几种:
将lambda函数赋值给一个变量,通过这个变量间接调用该lambda函数。
例如,执行语句add=lambda x, y: x+y,定义了加法函数lambda x, y: x+y,并将其赋值给变量add,这样变量add便成为具有加法功能的函数。例如,执行add(1,2),输出为3。
将lambda函数赋值给其他函数,从而将其他函数用该lambda函数替换。
例如,为了把标准库time中的函数sleep的功能屏蔽(Mock),我们可以在程序初始化时调用:time.sleep=lambda x:None。这样,在后续代码中调用time库的sleep函数将不会执行原有的功能。例如,执行time.sleep(3)时,程序不会休眠3秒钟,而是什么都不做。
3. 将lambda函数作为其他函数的返回值,返回给调用者。
函数的返回值也可以是函数。例如return lambda x, y: x+y返回一个加法函数。这时,lambda函数实际上是定义在某个函数内部的函数,称之为嵌套函数,或者内部函数。对应的,将包含嵌套函数的函数称之为外部函数。内部函数能够访问外部函数的局部变量,这个特性是闭包(Closure)编程的基础,在这里我们不展开。
4. 将lambda函数作为参数传递给其他函数。
部分Python内置函数接收函数作为参数。典型的此类内置函数有这些。
filter函数。此时lambda函数用于指定过滤列表元素的条件。例如filter(lambda x: x % 3 == 0, [1, 2, 3])指定将列表[1,2,3]中能够被3整除的元素过滤出来,其结果是[3]。
sorted函数。此时lambda函数用于指定对列表中所有元素进行排序的准则。例如sorted([1, 2, 3, 4, 5, 6, 7, 8, 9], key=lambda x: abs(5-x))将列表[1, 2, 3, 4, 5, 6, 7, 8, 9]按照元素与5距离从小到大进行排序,其结果是[5, 4, 6, 3, 7, 2, 8, 1, 9]。
map函数。此时lambda函数用于指定对列表中每一个元素的共同操作。例如map(lambda x: x+1, [1, 2,3])将列表[1, 2, 3]中的元素分别加1,其结果[2, 3, 4]。
reduce函数。此时lambda函数用于指定列表中两两相邻元素的结合条件。例如reduce(lambda a, b: ‘{}, {}’.format(a, b), [1, 2, 3, 4, 5, 6, 7, 8, 9])将列表 [1, 2, 3, 4, 5, 6, 7, 8, 9]中的元素从左往右两两以逗号分隔的字符的形式依次结合起来,其结果是’1, 2, 3, 4, 5, 6, 7, 8, 9’。
另外,部分Python库函数也接收函数作为参数,例如gevent的spawn函数。此时,lambda函数也能够作为参数传入。
Argument
事实上,关于lambda在Python社区是存在争议的。Python程序员对于到底要不要使用lambda意见不一致。
支持方认为使用lambda编写的代码更紧凑,更“pythonic”。
反对方认为,lambda函数能够支持的功能十分有限,其不支持多分支程序if…elif…else…和异常处理程序try …except…。并且,lambda函数的功能被隐藏,对于编写代码之外的人员来说,理解lambda代码需要耗费一定的理解成本。他们认为,使用for循环等来替代lambda是一种更加直白的编码风格。