lambda 注意点

(lambda  (parameter)  body)

Lambda表达式可以视为特殊的函数名,名字本身就直接描述函数的用途。这就是为啥可以用带#'lambda表达式来代替一个函数名。同时他跟单纯的函数名有区别。

比如你直接输入plot,他怎么会知道这个就是个函数名字呢?所以会说plot unbound,你必须得指明它是一个函数对象#'

注意点:下面这个例子也说明了,无论你lambda前有没有#'它的值都是一个lambda对象,即函数对象。

CL-USER> (defun plot(fn min max step)
       	    (loop for i from min to max by step do
       		(loop repeat (funcall #'fn i) do(format t "*"))
        	(format t "~%")))
PLOT
CL-USER> plot
; No value
CL-USER> #'plot
#<FUNCTION PLOT>
CL-USER> (funcall #'(lambda (x y) (+ x y)) 2 3)
5
CL-USER> (lambda (x y) (+ x Y))     #<FUNCTION (LAMBDA (X Y)) {24F29995}>
CL-USER> #'(lambda (x y) (+ x y))   #<FUNCTION (LAMBDA (X Y)) {2525B7D5}>

易错点:

1:函数中定义lambda.

请看下面程序,我原想着当我调用fn函数的时候可以更改*test* 的值,但是最后返回了一个lambda 对象,然后我就试着是不是因为没有输入funcall 的原因,谁知同样是不会改变*test*的值,其实(fn)就是求fn得值,跟(funcall fn)一样,都是会返回一个fn最后表达式的值。也就是lambda对象,我其实是想让这段逻辑执行掉,现在就是不执行。实际上是缺少了在对他进行一次funcall

并且你会注意到setf 中第二个参数实际上应该在第一次调用(fn)时就执行到了,所以才会在后来无论你再调用多少次都是5的原因。

如果想从根本上解决这个问题就是在函数fn定义的时候就funcall lambda对象,因为lambda的值是一个函数对象,你再funcall 一下,就相当于执行它了,也就是式中最后一个表达式的值了。

总之如果注意到(fn)/(funcall fn)值都是一个lambda对象的话,就应该顺着想到再用funcall 执行一次


2:let 中调用lambda,第一个代码中永远也不会执行lambda,因为你如果在let外层调用一个funcall的话,他会报错,因为本身let的值是最后一个表达式的值,如下两个都是一个数字,没有办法再被funcall了,如果let最后一个表达式是lambda对象的话,见3分析。

CL-USER> (let ((i 3))
	   		(setf i 4)
	   		(format t "~s~%" i)
	   		(lambda ()(setf i 5))
	   		(format t "~s~%" i))
4
4

CL-USER> (let ((i 3))
	   		(setf i 4)
	   		(format t "~s~%" i)
	  		(funcall (lambda ()(setf i 5)))
	  		(format t "~s~%" i))
4
5

3:调用*fn*(funcall (fn))对比一下,你会发现第一次调用的话,大家所有的代码都执行了,而*fn*在第二次执行的时候,format就不在执行了,而是只是执行lambda代码,而(funcall (fn))(fn)都同样执行了Lambda以上的输出逻辑。但是不要忘了,*fn*是一个全局变量可以存储的,而(funcall (fn))你的执行得到(fn)的值,而得到(fn)值就是执行全部的过程,除了最后把最后一个表达式的值返回过去,然后接着被funcall调用。你或者从另一个角度考虑,因为你是看着这个式子所以你知道(fn)的值就是lambda 对象,然后你就让funcall 调用这个对象得到一个值就行了,如果这样的话,即使是不同的实参他们的结果也都一样了,因为中间过程你一个都没有执行啊。每一次执行(funcall (fn))他们互不影响,他们彼此之间是独立事件。所以都会从内部开始直到执行到最外层。

CL-USER> (defparameter *fn* (let ((count 0))
			      (format t "~s2~%" count)
			      #'(lambda ()(setf count (1+ count)))))
02   ---为啥就有一值,
*FN*
CL-USER> (funcall *fn*)
1    --format 的跑哪去了?
CL-USER> (funcall *fn*)
2	

CL-USER> (defun fn()
	   (let ((i 3))
		    (setf i 4)
		    (format t "~s~%" i)
		    (lambda ()(setf i 5))
		    (format t "~s~%" i)
		    (lambda ()(setf i 6))
		    (format t "~s~%" i)
		    (lambda ()(setf *test* 99))))
STYLE-WARNING: redefining COMMON-LISP-USER::FN in DEFUN
FN
CL-USER> (fn)
4
4
4
#<FUNCTION (LAMBDA () :IN FN) {249CE465}>
CL-USER> (funcall (fn))
4
4
4
99
4:上面的例子3又牵涉到一个变量的词法作用域的闭包的概念。

当只有一个let表达式的时候,很明显count会作为一个词法变量可以被setf访问,这个时候就有个问题了。因为lambda对象可以作为一个参数进行传递,假如一个函数接受了这个lambda对象,也就是说lambda已经不再let的范围内了,那么本来的绑定count怎么说呢?这个也就如*fn* 所显示的那样,每一次的执行,都是建立在上一次的值的结果之上。count这个最初在let中的绑定,将会尽可能的保存下来,只要有一个地方保持了对let所返回的函数对象lambda的引用就行。

与上面相对的就是函数fn(), (funcall (fn)),虽然(fn)也返回了一个函数对象lambda,但是对这个lambda的引用也仅仅存在于这个函数,当函数结束以后也就什么都没有啦。



<think>我们首先讲解lambda表达式的知识Lambda表达式是Java8引入的一个重要特性,它使得我们能够以更简洁的方式表示匿名函数(anonymousfunctions)。Lambda表达式主要用于实现函数式接口(FunctionalInterface),即只有一个抽象方法的接口。###Lambda表达式的语法Lambda表达式的基本语法如下:```(parameters)->expression```或```(parameters)->{statements;}```其中:-`parameters`是参数列表,参数类型可以省略(编译器可以推断类型),如果只有一个参数,括号也可以省略。-`->`是lambda运算符。-`expression`是单个表达式,计算后返回结果。-`{statements;}`是代码块,可以包含多条语句,如果有返回值,需要显式使用`return`。###函数式接口Lambda表达式需要与函数式接口一起使用。函数式接口是只有一个抽象方法的接口。Java提供了一些内置的函数式接口,如`Runnable`、`Callable`、`Comparator`,以及`java.util.function`包中的`Predicate`、`Function`、`Supplier`、`Consumer`等。###示例以下是一些使用lambda表达式的例子:1.**使用`Runnable`接口**```java//传统方式Runnabler1=newRunnable(){@Overridepublicvoidrun(){System.out.println("HelloWorld1");}};//使用lambda表达式Runnabler2=()->System.out.println("HelloWorld2");```2.**使用`Comparator`接口**```java//传统方式Comparator<String>comparator1=newComparator<String>(){@Overridepublicintcompare(Strings1,Strings2){returns1.compareTo(s2);}};//使用lambda表达式Comparator<String>comparator2=(s1,s2)->s1.compareTo(s2);```3.**使用`Predicate`接口(来自`java.util.function`)**`Predicate<T>`接口包含一个方法`booleantest(Tt)`,用于测试输入是否满足条件。```javaPredicate<String>isEmpty=s->s.isEmpty();System.out.println(isEmpty.test(""));//trueSystem.out.println(isEmpty.test("abc"));//false```###方法引用方法引用是lambda表达式的一种简写形式,用于直接指向已有的方法。方法引用有四种形式:1.静态方法引用:`ClassName::staticMethod`2.实例方法引用:`instance::instanceMethod`3.特定类型的任意对象的实例方法引用:`ClassName::instanceMethod`4.构造方法引用:`ClassName::new`例如:```java//静态方法引用Function<String,Integer>parseInt=Integer::parseInt;//实例方法引用Strings="Hello";Supplier<Integer>lengthSupplier=s::length;//特定类型的任意对象的实例方法引用Function<String,String>toUpperCase=String::toUpperCase;//构造方法引用Supplier<List<String>>listSupplier=ArrayList::new;```###Lambda表达式的作用-简化代码,尤其是匿名内部类的写法。-支持函数式编程,使得代码更易于并行处理(例如使用StreamAPI)。###注意事项-Lambda表达式只能引用final或等效final(即初始化后不再改变)的局部变量。-Lambda表达式中的`this`关键字指向的是创建lambda表达式的外部类的实例,而不是lambda表达式本身。###完整示例代码下面是一个使用lambda表达式和函数式接口的完整示例:```javaimportjava.util.Arrays;importjava.util.Comparator;importjava.util.List;importjava.util.function.Predicate;publicclassLambdaExample{publicstaticvoidmain(String[]args){//使用Lambda表达式实现ComparatorList<String>names=Arrays.asList("Alice","Bob","Charlie","David");names.sort((s1,s2)->s1.compareTo(s2));System.out.println("Sortednames:"+names);//使用方法引用names.sort(String::compareToIgnoreCase);System.out.println("Sortedignoringcase:"+names);//使用PredicatePredicate<String>startsWithA=s->s.startsWith("A");System.out.println("Namesstartingwith'A':");names.stream().filter(startsWithA).forEach(System.out::println);}}```###解释1.我们首先创建了一个字符串列表`names`。2.使用lambda表达式作为参数传递给`sort`方法,按字典序排序。3.使用方法引用`String::compareToIgnoreCase`实现忽略大小写的排序。4.使用`Predicate`和StreamAPI过滤出以"A"开头的名字并打印。###§§相关问题§§:1.什么是函数式接口?请举几个Java内置的函数式接口的例子。2.Lambda表达式与匿名内部类的主要区别是什么?3.请解释方法引用的四种类型,并举例说明。4.在Lambda表达式中,为什么只能使用final或等效final的局部变量?</think>### Lambda表达式详解 Lambda表达式是Java 8引入的核心特性,用于简化函数式编程。它本质上是匿名函数的简洁表示,主要应用于函数式接口(只有一个抽象方法的接口)。 #### 核心语法 ```java (parameters) -> expression ``` 或 ```java (parameters) -> { statements; } ``` #### 关键特性: 1. **参数类型推断**:编译器可自动推断参数类型 2. **单表达式简写**:单行表达式可省略`return`和花括号 3. **函数式接口**:必须匹配目标接口的函数签名 --- ### 代码示例:Lambda使用场景 #### 1. 替代匿名内部类(Runnable) ```java // 传统方式 new Thread(new Runnable() { @Override public void run() { System.out.println("传统方式"); } }).start(); // Lambda方式 new Thread(() -> System.out.println("Lambda方式")).start(); ``` #### 2. 集合操作(List遍历) ```java List<String> names = Arrays.asList("Alice", "Bob", "Charlie"); // 传统遍历 for (String name : names) { System.out.println(name); } // Lambda遍历 names.forEach(name -> System.out.println(name)); // 方法引用(更简洁) names.forEach(System.out::println); ``` #### 3. 函数式接口(Comparator) ```java List<Integer> numbers = Arrays.asList(5, 3, 8, 2); // 传统排序 Collections.sort(numbers, new Comparator<Integer>() { @Override public int compare(Integer a, Integer b) { return a - b; } }); // Lambda排序 Collections.sort(numbers, (a, b) -> a - b); // 方法引用排序 Collections.sort(numbers, Integer::compare); ``` #### 4. Stream API结合 ```java List<String> result = names.stream() .filter(name -> name.startsWith("A")) // 过滤 .map(String::toUpperCase) // 转换 .sorted() // 排序 .collect(Collectors.toList()); // 收集结果 ``` --- ### 函数式接口示例 自定义函数式接口: ```java @FunctionalInterface interface StringProcessor { String process(String input); // 允许有默认方法 default void log() { System.out.println("日志记录"); } } public class Main { public static void main(String[] args) { // Lambda实现接口 StringProcessor reverse = s -> new StringBuilder(s).reverse().toString(); System.out.println(reverse.process("hello")); // 输出 "olleh" reverse.log(); // 输出 "日志记录" } } ``` --- ### Lambda表达式原理 编译后Lambda表达式会被转换为: 1. 生成私有静态方法(包含Lambda体逻辑) 2. 生成`invokedynamic`指令调用 3. 运行时使用`LambdaMetafactory`生成函数式接口实例 例如: ```java Function<String, Integer> length = s -> s.length(); ``` 编译后等价于: ```java // 编译器生成的方法 private static int lambda$main$0(String s) { return s.length(); } // 运行时动态生成实现类 Function<String, Integer> length = (Function<String, Integer>) LambdaMetafactory.metafactory(...); ``` --- ### 最佳实践 1. **保持简短**:Lambda体应不超过3行 2. **避免状态修改**:遵循函数式编程的无状态原则 3. **使用方法引用**:当直接调用现有方法时 4. **明确参数名称**:复杂Lambda应使用有意义的参数名 ```java // 不推荐(过于复杂) names.forEach(s -> { String formatted = s.toUpperCase(); System.out.printf("%s: %d%n", formatted, s.length()); }); // 推荐(提取方法) names.forEach(LambdaDemo::printNameDetails); private static void printNameDetails(String name) { String formatted = name.toUpperCase(); System.out.printf("%s: %d%n", formatted, name.length()); } ``` --- ### 注意事项 1. **变量捕获**:只能访问`final`或等效`final`的局部变量 2. **`this`关键字**:Lambda中的`this`指向外部类,而非Lambda自身 3. **异常处理**:需在Lambda内处理受检异常 4. **避免重载**:带Lambda参数的方法重载易导致歧义 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值