什么是Lambda表达式

Lambda 表达式(lambda expression)是一个匿名函数,简化我们调用匿名函数的过程。
Lambda 好处:简化我们匿名内部类的调用。
Lambda+方法引用可以使代码变得更加精简。

为什么要使用Lambda表达式


可以以非常简洁的形式调用我们的匿名函数接口。

  1. public static void main(String[] args) {
  2. // 1.使用new的实现类的形式调用接口
  3. OrderService orderSerivce1 = new OrderSerivceImpl();
  4. orderSerivce1.addOrder();
  5. // 2.使用匿名内部接口调用
  6. new OrderService() {
  7. @Override
  8. public void addOrder() {
  9. System.out.println("使用匿名内部类的形式调用接口");
  10. }
  11. }.addOrder();
  12. // 3.使用lambda调用接口
  13. OrderService orderSerivce2= ()-> System.out.println("使用lambda调用接口");
  14. orderSerivce2.addOrder();
  15. }

Lambda表达式的规范

函数式接口:也称 SAM 接口,即 Single Abstract Method interfaces,有且只有一个抽象方法,但可以有多个非抽象方法的接口

  1. 在接口中只能够允许有一个抽象方法
  2. 在函数接口中可以定义object类中方法
  3. 可以使用默认或者静态方法
  4. @FunctionalInterface 标记该接口为函数式接口

在 java 8 中专门有一个包放函数式接口java.util.function,该包下的所有接口都有 @FunctionalInterface 注解,提供函数式编程。

在其他包中也有函数式接口,其中一些没有@FunctionalInterface 注解,但是只要符合函数式接口的定义就是函数式接口,与是否有@FunctionalInterface注解无关,注解只是在编译时起到强制规范定义的作用。其在 Lambda 表达式中有广泛的应用。

Lambda基础语法

()-> {}

  1. ()—————— 参数列表
  2. -> —————— 分隔符
  3. {} —————— 方法体

精简语法:

  • 一个参数无需定义圆括号,但多个参数需要定义圆括号
  • 如果主体只包含了一个语句,就不需要使用大括号
  • 如果方法体只有一条return的情况下不需要些{} 和return
  • 不需要声明参数类型,编译器可以统一识别参数值

    无参方法调用

    ```java @FunctionalInterface public interface AcanthopanaxInterface { void get(); }

@Test public void acanthopanaxInterfaceTest(){ // AcanthopanaxInterface acanthopanaxInterface = () -> { // System.out.println(“使用lamdba表达式调用方法”); // }; // acanthopanaxInterface.get(); ((AcanthopanaxInterface)() -> System.out.println(“使用lamdba表达式调用方法”)).get(); }

  1. <a name="UXnow"></a>
  2. ## 带参数和返回值
  3. ```java
  4. @FunctionalInterface
  5. public interface YunInterface {
  6. String get(int i, int j);
  7. }
  8. @Test
  9. public void YunInterfaceTest(){
  10. // 1.使用匿名内部类调用有参数函数方法
  11. // String result = new YunInterfaceTest() {
  12. // @Override
  13. // public String get(int i, int j) {
  14. // return i + "-" + j;
  15. // }
  16. // }.get(1, 1);
  17. // System.out.println(result);
  18. //2.使用lamdba 调用有参数函数方法
  19. String result = ((YunInterface) (i, j) -> {
  20. System.out.println("参数:" + i + "," + j);
  21. return i + "-" + j;
  22. }).get(1, 1);
  23. System.out.println(result);
  24. }
  25. }

方法引用

方法引用可以认为是Lambda表达式的一种特殊形式,Lambda表达式可以让开发者自定义抽象方法的实现代码,方法引用则可以让开发者直接引用已存在的实现方法,作为Lambda表达式的Lambda体

方法引用的操作符是双冒号”::

  1. 构造器引用: 类名::new
  2. 静态方法引用: 类名::静态方法名称
  3. 特定对象的实例方法引用:对象实例(new 对象)::方法
  4. 对象方法的特殊引用: 类名:: 实例方法名称

需要遵循一个规范:

方法引用 的方法参数列表、返回类型与函数式接口的参数列表、返回类型必须要保持一致

构造器引用

造器引用的语法格式为: 类名::new ,如() -> new ArrayList<String>()等价于 ArrayList<String>::new,代码示例:

  1. @FunctionalInterface
  2. public interface ListInterface {
  3. List getList();
  4. }
  5. ListInterface listInterface = () -> new ArrayList<>();

等价于

  1. ListInterface listInterface = ArrayList::new;

构造器引用适用于lambda表达式主体中仅仅调用了某个类的构造函数返回实例的场景

静态方法引用

静态方法引用的语法格式为: 类名::静态方法名 ,如System.out::println等价于lambda表达式s -> System.out.println(s),代码示例

  1. public class MethodReference {
  2. public static void main(String[] args) {
  3. // 1.使用匿名内部类的形式 调用get方法
  4. // new MessageInterface() {
  5. // @Override
  6. // public void get() {
  7. // MethodReference.getMethod();
  8. // }
  9. // }.get();
  10. MessageInterface messageInterface2 = () -> {
  11. MethodReference.getStaticMethod();
  12. };
  13. messageInterface2.get();
  14. // 使用方法引入调用方法 必须满足:方法引入的方法必须和函数接口中的方法参数列表/返回值一定保持一致。
  15. MessageInterface messageInterface = MethodReference::getStaticMethod;
  16. messageInterface.get();
  17. }
  18. /**
  19. * 静态方法引入
  20. */
  21. public static void getStaticMethod() {
  22. System.out.println("我是 getMethod");
  23. }
  24. }}
  25. @FunctionalInterface
  26. public interface MessageInterface {
  27. void get();
  28. }

静态方法引用适用于lambda表达式主体中仅仅调用了某个类的静态方法的情形

特定对象的实例方法引用

特定对象的实例方法引用的语法格式为: 对象::实例方法名 , 示例代码

  1. public class Test
  2. {
  3. public static void main(String[] args)
  4. {
  5. Test test = new Test();
  6. // lambda表达式使用:
  7. Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(s -> test.println(s));
  8. // 特定对象的实例方法引用:
  9. Arrays.asList(new String[] {"a", "c", "b"}).stream().forEach(test::println);
  10. }
  11. public void println(String s)
  12. {
  13. System.out.println(s);
  14. }
  15. }

特定对象的实例方法引用适用于lambda表达式的主体中仅仅调用了某个对象的某个实例方法的场景

如何理解System.out::println?
先看System.out.println()

  1. public final class System {
  2. ...
  3. public final static PrintStream out = null;
  4. ...
  5. }

System是java.lang里面的一个类,而out就是System里面的一个静态数据成员,而且这个成员是java.io.PrintStream类的引用,被关键字static修饰的成员可以直接通过”类名.成员名”来引用,而无需创建类的实例。所以System.out是调用了System类的静态数据成员out
println()就是java.io.PrintStream类里的一个方法,它的作用是向控制台输出信息。因为System.outjava.io.PrintStream类的实例的引用,所以可以通过 System.out.println()来调用此方法
所以
System.out::println相当于PrintStream类的实例::println

对象方法的特殊引用

也叫特定类的任意对象的方法引用
对象方法的特殊引用的语法格式为: 类名::实例方法名
如果在使用lambda表达式实现某些接口时,lambda表达式中包含了一个对象,此时方法体中直接使用这个对象调用它的某一个方法就可以完成整个逻辑。其他的参数,可以作为调用方法的参数。此时,可以对这种实现就行简化

  1. public static void main(String[] args) {
  2. //如果对于这个方法的实现逻辑,是为了得到对象的name
  3. GetField getField = people -> people.getName();
  4. //对于这个方法的特殊引用
  5. GetField getField1 = People::getName;
  6. //如果对于这个方法的实现逻辑,是为了给对象的某些属性赋值
  7. SetField setField = (people, name) -> people.setName(name);
  8. //对于这个方法的特殊引用
  9. SetField setField1 = People::setName;
  10. }
  11. interface GetField{
  12. void get(People people);
  13. }
  14. interface SetField{
  15. void set(People people,String name);
  16. }

可以发现:
如果对于某个方法的实现逻辑,刚好是参数对象的某一个方法(即lambda表达式的第一个入参为实例方法的调用者,后面的入参与实例方法的入参一致),那么就可以参数对象类名::方法名引用

访问局部变量

我们可以直接在 lambda 表达式中访问外部的局部变量:

  1. @Test
  2. public void scopeTest(){
  3. final int num = 2;
  4. int result = ((YunInterface)(i,j) -> i+j+num).get(1,1);//4
  5. }

但是和匿名对象不同的是,这里的变量num可以不用声明为final,该代码同样正确:

  1. @Test
  2. public void scopeTest(){
  3. int num = 2;
  4. int result = ((YunInterface)(i,j) -> i+j+num).get(1,1);//4
  5. }

不过这里的 num 必须不可被后面的代码修改(即隐性的具有final的语义),例如下面的就无法编译:

  1. @Test
  2. public void scopeTest(){
  3. int num = 2;
  4. int result = ((YunInterface)(i,j) -> i+j+num).get(1,1);//4
  5. num=4;
  6. }

Lambda案例

forEach

  1. ArrayList<String> strings = new ArrayList<>();
  2. strings.add("tifa");
  3. strings.add("Aerith");
  4. strings.add("Cloud");
  5. // strings.forEach(new Consumer() {
  6. // @Override
  7. // public void accept(Object o) {
  8. // System.out.println("o:" + o);
  9. // }
  10. // });
  11. // strings.forEach((o) -> {
  12. // System.out.println(o);
  13. // });
  14. strings.forEach(System.out::println)

Lambda集合排序

  1. @Data
  2. public class UserEntity {
  3. private String name;
  4. private Integer age;
  5. }
  6. ArrayList<UserEntity> userlists = new ArrayList<>();
  7. userlists.add(new UserEntity("tifa", 16));
  8. userlists.add(new UserEntity("Aerith", 22));
  9. userlists.add(new UserEntity("Cloud", 24));
  10. // userlists.sort(new Comparator<UserEntity>() {
  11. // @Override
  12. // public int compare(UserEntity o1, UserEntity o2) {
  13. // return o1.getAge() - o2.getAge();
  14. // }
  15. // });
  16. userlists.sort((o1, o2) ->
  17. o1.getAge() - o2.getAge()
  18. );
  19. userlists.forEach((Consumer) o -> System.out.println(o));

线程调用

  1. new Thread(()-> System.out.println("我是子线程")).start();

在工作中,常常会在使用MyBatis-Plus的条件构造器时使用到Lambda

  1. //查询
  2. baseMapper.selectList(
  3. new QueryWrapper<SupplyEntity>()
  4. .lambda()
  5. .eq(SupplyEntity::getCardId, cardBasicInfoVM.getId())
  6. .orderByDesc(SupplyEntity::getCreateTime));
  7. //删除
  8. entryStatService.delete(
  9. new QueryWrapper<CardEntryStat>()
  10. .lambda()
  11. .eq(CardEntryStat::getShareType, shareType)
  12. );