Lambda表达式-使用说明

公众号:

公众号二维码

     

   jdk8已经发布4年,其中有一个特性:Lambda,它是一个令开发者便捷开发的一种方式,Lambda Expression (Lambda表达式)是为了让java提供一种面向函数编程,原本在jdk8之前只支持面向对象编程,

而函数式编程则是对行为的抽象(将行为作为一个参数进行传递),接下来通过例子来说明:

这是一个线程对象,传入匿名内部类的例子:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Hello World!");
    }
});

IDEA会给出提示可以使用Lambda表达式替换。

通过使用Lambda表达式则只需要使用一句话就可代替上面使用匿名类的方式。

new Thread(() -> System.out.println("Hello World!"));

  

在这个例子中,传统的语法规则,我们是将一个匿名内部类作为参数进行传递,我们实现了Runnable接口,并将其作为参数传递给Thread类,这实际上我们传递的是一段代码,也即我们将代码作为了数据进行传递,这就带来许多不必要的“样板代码”。

  Lambda表达式一共有三部分组成:

  后面的示例中我们会详解这个结构,包括有无参数,有无返回值的问题。 那么这个看起来奇奇怪怪的不太像Java的语法规则,其本身含义到底什么呢?这也是开始困扰我的问题,什么时候在什么场景下可以使用Lambda表达式。

  能够接收Lambda表达式的参数类型,是一个只包含一个方法的接口。只包含一个方法的接口称之为“函数接口”。

  例如上面创建一个线程的示例,Runnable接口只包含一个方法,所以它被称为“函数接口”,所以它可以使用Lambad表达式来代替匿名内部类。根据这个规则,我们试着来写一个函数接口,并使用Lambda表达式作为参数传递。

1 package com.coderbuff.custom;
2 
3 /**
4  * 函数接口:只有一个方法的接口。作为Lambda表达式的类型
5  * Created by Kevin on 2018/2/17.
6  */
7 public interface FunctionInterface {
8     void test();
9 }

   测试:

 1 package com.coderbuff.custom;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * 函数接口测试
 7  * Created by Kevin on 2018/2/17.
 8  */
 9 public class FunctionInterfaceTest {
10 
11     @Test
12     public void testLambda() {
13         func(new FunctionInterface() {
14             @Override
15             public void test() {
16                 System.out.println("Hello World!");
17             }
18         });
19         //使用Lambda表达式代替上面的匿名内部类
20         func(() -> System.out.println("Hello World"));
21     }
22 
23     private void func(FunctionInterface functionInterface) {
24         functionInterface.test();
25     }
26 }

  可以看到,只要是一个接口中只包含一个方法,则可以使用Lambda表达式,这样的接口称之为“函数接口”。

  上面的函数接口比较简单不包含参数,也不包含返回值

  我们再来修改FunctionInterface函数接口逐步加大Lambda表达式的难度——包含参数,不包含返回值

1 package com.coderbuff.custom;
2 
3 /**
4  * 函数接口:只有一个方法的接口。作为Lambda表达式的类型
5  * Created by Kevin on 2018/2/17.
6  */
7 public interface FunctionInterface {
8     void test(int param);
9 }

  测试:

 1 package com.coderbuff.custom;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * 函数接口测试
 7  * Created by Kevin on 2018/2/17.
 8  */
 9 public class FunctionInterfaceTest {
10 
11     @Test
12     public void testLambda() {
13         //使用Lambda表达式代替匿名内部类
14         func((x) -> System.out.println("Hello World" + x));
15     }
16 
17     private void func(FunctionInterface functionInterface) {
18         int x = 1;
19         functionInterface.test(x);
20     }
21 }

  关注Lambda表达式“(x) -> Sysout.out.println("Hello World" + x)”,左边传递的是参数,此处并没有指明参数类型,因为它可以通过上下文进行类型推导,但在有些情况下不能推导出参数类型(在编译时不能推导通常IDE会提示),此时则需要指明参数类型。我个人建议,任何情况下指明函数的参数类型

  哪种情况不能推导出参数类型呢?就是函数接口是一个泛型的时候。

 

1 package com.coderbuff.custom;
2 
3 /**
4  * 函数接口:只有一个方法的接口。作为Lambda表达式的类型
5  * Created by Kevin on 2018/2/17.
6  */
7 public interface FunctionInterface<T> {
8     void test(T param);
9 } 

 

  测试:

 

 1 package com.coderbuff.custom;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * 函数接口测试
 7  * Created by Kevin on 2018/2/17.
 8  */
 9 public class FunctionInterfaceTest {
10 
11     @Test
12     public void testLambda() {
13         //使用Lambda表达式代替匿名内部类
14         func((Integer x) -> System.out.println("Hello World" + x));
15     }
16 
17     private void func(FunctionInterface<Integer> functionInterface) {
18         int x = 1;
19         functionInterface.test(x);
20     }
21 }

 

  上面的示例提到了Lambda表达式的两种情况:

  无参数,无返回值;

  有参数,无返回值。

  接下来就是有参数,有返回值这种较为复杂的情况。

 

1 package com.coderbuff.custom;
2 
3 /**
4  * 函数接口:只有一个方法的接口。作为Lambda表达式的类型
5  * Created by Kevin on 2018/2/17.
6  */
7 public interface FunctionInterface<T> {
8     boolean test(T param);
9 }

 

   测试:

 

 1 package com.coderbuff.custom;
 2 
 3 import org.junit.Test;
 4 
 5 /**
 6  * 函数接口测试
 7  * Created by Kevin on 2018/2/17.
 8  */
 9 public class FunctionInterfaceTest {
10 
11     @Test
12     public void testLambda() {
13         //使用Lambda表达式代替匿名内部类
14         func((Integer x) -> true);
15     }
16 
17     private void func(FunctionInterface<Integer> functionInterface) {
18         int x = 1;
19         functionInterface.test(x);
20     }
21 }

 

  此时的Lambda表达式“(Integer x) -> true”,右边是表达式的主体,直接返回true,如果有多行代码,则可以直接使用花括号表示,例如:

func((Integer x) -> {
    System.out.println("Hello World" + x);
    return true;
});

  Lambda表达式基本的语法规则:

  无参数,无返回值;

  有参数,无返回值;

  有参数,有返回值。

  这三种基本情况已经大致清楚了,特别是需要弄清,什么时候可以使用Lambda表达式代替匿名内部类,也就是Lambda表达式的应用场景是函数接口。Lambda表达式这一新特性在JDK8中的引入,更大的好处则是集合API的更新,新增的Stream类库,使得我们在遍历使用集合时不再像以往那样不断地使用for循环。

 

JDK8使用集合的正确姿势

  示例:计算来自“chengdu”的学生数量有多少。

  在JDK8前的代码:

for (Student student : studentList) {
    if (student.getCity().equals("chengdu")) {
        count++;
    }
}

  JDK8使用集合的正确姿势:

count = studentList.stream().filter((student -> student.getCity().equals("chengdu"))).count();

  API的使用“难度”恰似提高了,实际只是不熟悉而已。传统迭代的方式需要阅读完整个循环才能明白代码逻辑,JDK8通过流的方式则可以望文生义且代码量大大减小。

  其中最为重要的是——Stream流。Stream的是通过函数式编程方式实现的在集合类上进行复杂操作的工具。若要详细讲解Stream的实现方式我相信再写一篇博客也不为过,所以此处不再考查Stream的内部实现。这里是想告诉大家,如果有幸使用JDK8的开发环境进行开发,尽量学习使用新的集合操作API。

 有关Lambda表达式的应用太多,并发编程、响应式编程等等。

 

本文引用:http://www.cnblogs.com/yulinfeng/p/8452379.html

 

<think>好的,用户现在问的是C++ lambda表达式的原理和使用。之前他问了智能指针,现在转向lambda,可能是在深入学习C++的高级特性。我需要先回顾lambda的基本概念,然后深入原理,再讲使用场景和注意事项。 首先,用户之前得到过关于智能指针的详细回答,结构清晰,有分点说明,所以这次也应该保持类似的结构。用户可能希望了解lambda的实现原理,比如编译器如何处理lambda,以及它们与普通函数和函数对象的关系。同时,使用部分需要包括语法、捕获列表、常见用例等。 需要注意用户可能的背景:已经了解C++基础,正在学习更高级的特性。因此,解释时要避免过于基础的内容,但也不能假设他们已有lambda的知识。需要平衡深度和易懂性。 需要检查是否有常见的误区,比如捕获列表的值捕获和引用捕获的区别,lambda的生命周期问题,还有mutable关键字的作用。此外,提到lambda的类型是唯一的,无法用普通类型名声明,可能需要用auto或者模板参数。 在原理部分,要说明lambda实质上是生成一个匿名类,重载了operator(),捕获的变量成为这个类的成员变量。这样每次调用lambda时,实际上是调用该对象的operator()。对于捕获方式的不同,类成员初始化的方式也不同,值捕获是复制,引用捕获是存储引用。 使用场景方面,可以举STL算法中的例子,比如sort、for_each,以及异步编程中的回调函数,比如线程启动时传递lambda。还可以提到在需要局部定义函数时的便利性,比如避免定义全局函数或仿函数类。 注意事项中,要强调生命周期的问题,尤其是捕获引用时可能导致悬垂引用。还有性能方面,lambda的inline特性可能带来的优化,以及捕获大对象时的开销考虑。 需要确保例子正确,比如展示不同捕获方式的语法,mutable的使用,以及lambda作为函数参数的情况。此外,提到C++17引入的constexpr lambda和C++20的模板参数支持,显示对最新标准的了解,但可能用户更关注基础,所以可以简要提及。 最后,总结部分要对比lambda与函数指针、函数对象的优缺点,突出lambda的便捷性和适用场景。确保回答结构清晰,分点明确,帮助用户逐步理解lambda的原理和使用方法。</think>#### C++ Lambda表达式原理及使用指南 --- ### 一、Lambda表达式核心原理 Lambda表达式是**可内联的匿名函数对象**,编译器会将其转换为一个**匿名类**,核心机制如下: 1. **底层实现** 编译器为每个Lambda生成一个**匿名类**(称为闭包类型),包含: - **成员变量**:捕获的外部变量(值捕获时复制,引用捕获时存储引用) - **重载的 `operator()`**:实现Lambda函数体逻辑 ```cpp // 示例:auto lambda = [x](int y){ return x + y; }; // 等效生成的类: class __AnonymousClass { private: int x; // 值捕获的变量 public: __AnonymousClass(int x) : x(x) {} int operator()(int y) const { return x + y; } }; ``` 2. **捕获机制** - **值捕获**:复制外部变量到闭包对象中 `[x]` → 生成类成员变量 `int x` - **引用捕获**:存储外部变量的引用 `[&x]` → 生成类成员变量 `int& x` - **隐式捕获**:`[=]`(全值捕获)、`[&]`(全引用捕获) 3. **类型特性** - 每个Lambda表达式类型**唯一**,只能用 `auto` 或模板参数声明 - 可隐式转换为函数指针(当不捕获外部变量时) --- ### 二、Lambda语法结构 基本格式: $$ \text{[捕获列表] (参数列表) -> 返回类型 \{ 函数体 \}} $$ #### 关键组成部分: 1. **捕获列表(Capture List)** | 语法 | 行为 | 示例 | |---------------|-------------------------------|-----------------------| | `[]` | 不捕获任何变量 | `[] { return 42; }` | | `[x]` | 值捕获变量x | `[x] { return x+1; }`| | `[&x]` | 引用捕获变量x | `[&x] { x=0; }` | | `[=]` | 隐式值捕获所有外部变量 | `[=] { return a+b; }`| | `[&]` | 隐式引用捕获所有外部变量 | `[&] { a++; b++; }` | | `[this]` | 捕获当前对象的 `this` 指针 | `[this] { do_something(); }` | 2. **参数列表与返回类型** - 参数列表类似普通函数参数(可省略空参数列表的 `()`) - 返回类型可通过 `->` 显式指定,或由编译器自动推导 3. **`mutable` 关键字** 允许修改值捕获的变量(默认 `operator()` 为 `const`): ```cpp int x = 10; auto lambda = [x]() mutable { x++; }; // 允许修改x的副本 ``` --- ### 三、典型使用场景 #### 1. **STL算法中的回调函数** ```cpp std::vector<int> nums {3, 1, 4, 1, 5}; // 使用Lambda排序 std::sort(nums.begin(), nums.end(), [](int a, int b) { return a > b; }); // 遍历输出(结合捕获) int threshold = 2; std::for_each(nums.begin(), nums.end(), [&threshold](int n) { if (n > threshold) std::cout << n << " "; }); ``` #### 2. **异步编程与回调** ```cpp // 线程启动时传递Lambda std::thread t([=] { std::cout << "Thread ID: " << std::this_thread::get_id() << std::endl; }); // 定时器回调 timer.async_wait([](const boost::system::error_code& ec) { if (!ec) handle_timeout(); }); ``` #### 3. **局部函数封装** ```cpp void process_data(const std::vector<int>& data) { // 定义局部逻辑 auto validator = [](int x) { return x > 0 && x < 100; }; if (std::all_of(data.begin(), data.end(), validator)) { // 执行操作 } } ``` --- ### 四、注意事项与进阶特性 1. **生命周期管理** - 引用捕获的变量必须确保在Lambda执行时仍有效 ```cpp // 危险示例:捕获局部变量的引用 std::function<void()> create_lambda() { int x = 10; return [&x]() { std::cout << x; }; // x离开作用域后悬空引用 } ``` 2. **性能优化** - 编译器可将Lambda内联优化(类似普通函数) - 避免在频繁调用的代码中捕获大型对象(值捕获可能产生拷贝开销) 3. **C++17/20 扩展** - **`constexpr` Lambda**(C++17):可在编译期求值 ```cpp constexpr auto square = [](int n) { return n * n; }; static_assert(square(5) == 25); ``` - **模板参数支持**(C++20): ```cpp auto lambda = []<typename T>(T a, T b) { return a + b; }; ``` --- ### 五、与函数指针/函数对象对比 | 特性 | Lambda表达式 | 函数指针 | 函数对象(仿函数) | |---------------------|------------------------|------------------|---------------------| | 捕获外部变量 | ✔️(通过捕获列表) | ❌ | ✔️(通过类成员) | | 内联优化 | ✔️ | 依赖编译器 | ✔️ | | 类型唯一性 | ✔️ | ❌ | ❌ | | 代码简洁性 | ✔️ | ✔️ | ❌(需定义类) | --- ### 总结 **核心价值**: - 在需要**局部定义函数逻辑**时提供简洁语法 - 支持**灵活捕获上下文变量**,减少全局状态依赖 - 与STL算法、现代C++特性(如线程、异步)深度集成 **使用建议**: - 优先使用Lambda替代传统的仿函数(除非需要复杂状态管理) - 注意捕获方式的选择(值捕获 vs 引用捕获) - 在需要跨作用域传递时,谨慎管理生命周期(推荐值捕获或 `shared_ptr` 封装)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值