Java Lambda

1、Lambda表达式简介

Lambda 表达式,也称为闭包,是 Java 8 中最大和最令人期待的语言改变。它允许将函数当成参数传递给某个方法,或者把代码本身当作数据处理,函数式开发者非常熟悉这些概念。
很多JVM平台上的语言(Groovy、Scala等)从诞生之日就支持 Lambda 表达式,但是 Java 开发者没有选择,只能使用匿名内部类代替Lambda表达式。

  1. //匿名内部类方式排序
  2. List<String> names = Arrays.asList( "a", "b", "d" );
  3. Collections.sort(names, new Comparator<String>() {
  4. @Override
  5. public int compare(String s1, String s2) {
  6. return s1.compareTo(s2);
  7. }
  8. });

Lambda 的设计可谓耗费了很多时间和很大的社区力量,最终找到一种折中的实现方案,可以实现简洁而紧凑的语言结构。
Lambda 表达式的语法格式:

  1. (parameters) -> expression
  2. (parameters) ->{ statements; }

Lambda 编程风格,可以总结为四类:

  • 可选类型声明:不需要声明参数类型,编译器可以统一识别参数值
  • 可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号
  • 可选的大括号:如果主体包含了一个语句,就不需要使用大括号
  • 可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值

    2、Lambda表达式的编程风格

    2.1 可选类型声明

    在使用过程中,可以不用显示声明参数类型,编译器可以统一识别参数类型,例如:
    1. Collections.sort(names, (s1, s2) -> s1.compareTo(s2));
    上面代码中的参数s1s2的类型是由编译器推理得出的,也可以显式指定该参数的类型,例如:
    1. Collections.sort(names, (String s1, String s2) -> s1.compareTo(s2));
    运行之后,两者结果一致!

    2.2 可选的参数圆括号

    当方法那只有一个参数时,无需定义圆括号,例如:
    1. Arrays.asList( "a", "b", "d" ).forEach( e -> System.out.println( e ) );
    但多个参数时,需要定义圆括号,例如:
    1. Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );

    2.3 可选的大括号

    当主体只包含了一行时,无需使用大括号,例如:
    1. Arrays.asList( "a", "b", "c" ).forEach( e -> System.out.println( e ) );
    当主体包含多行时,需要使用大括号,例如:
    1. Arrays.asList( "a", "b", "c" ).forEach( e -> {
    2. System.out.println( e );
    3. System.out.println( e );
    4. } );

    2.4 可选的返回关键字

    如果表达式中的语句块只有一行,则可以不用使用return语句,返回值的类型也由编译器推理得出,例如:
    1. Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> e1.compareTo( e2 ) );
    如果语句块有多行,可以在大括号中指明表达式返回值,例如:
    1. Arrays.asList( "a", "b", "d" ).sort( ( e1, e2 ) -> {
    2. int result = e1.compareTo( e2 );
    3. return result;
    4. } );

    2.5 变量作用域

    还有一点需要了解的是,Lambda 表达式可以引用类成员和局部变量,但是会将这些变量隐式得转换成final,例如:
    1. String separator = ",";
    2. Arrays.asList( "a", "b", "c" ).forEach(
    3. ( String e ) -> System.out.print( e + separator ) );
    1. final String separator = ",";
    2. Arrays.asList( "a", "b", "c" ).forEach(
    3. ( String e ) -> System.out.print( e + separator ) );
    两者等价!
    同时,Lambda 表达式的局部变量可以不用声明为final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义),例如:
    1. int num = 1;
    2. Arrays.asList(1,2,3,4).forEach(e -> System.out.println(num + e));
    3. num =2;
    4. //报错信息:Local variable num defined in an enclosing scope must be final or effectively final
    在 Lambda 表达式当中不允许声明一个与局部变量同名的参数或者局部变量,例如:
    1. int num = 1;
    2. Arrays.asList(1,2,3,4).forEach(num -> System.out.println(num));
    3. //报错信息:Variable 'num' is already defined in the scope

    3、与Lambda表达式处理的对比案例

    A.匿名内部类和Lambda表达式比较

    ```java // 原来的匿名内部类 @Test public void comparatorTest() { Comparator comparator = new Comparator() {
    1. @Override
    2. public int compare(Integer o1, Integer o2) {
    3. return Integer.compare(o1, o2);
    4. }
    }; TreeSet treeSet = new TreeSet<>(comparator); }

// Lambda表达式的写法 @Test public void lambdaTest() { Comparator comparator = (x, y) -> Integer.compare(x, y); TreeSet treeSet = new TreeSet<>(comparator); }

  1. <a name="q5Vhk"></a>
  2. ## B.需求:对员工进行条件查询
  3. <a name="MLDuT"></a>
  4. ### Employee.java
  5. ```java
  6. package com.fcant.java8.lambda.bean;
  7. /**
  8. * Employee
  9. * <p>
  10. * encoding:UTF-8
  11. *
  12. * @author Fcant 下午 21:20:09 2020/2/18/0018
  13. */
  14. public class Employee {
  15. private String name;
  16. private int age;
  17. private double salary;
  18. public Employee() {
  19. }
  20. public Employee(String name, int age, double salary) {
  21. this.name = name;
  22. this.age = age;
  23. this.salary = salary;
  24. }
  25. public String getName() {
  26. return name;
  27. }
  28. public void setName(String name) {
  29. this.name = name;
  30. }
  31. public int getAge() {
  32. return age;
  33. }
  34. public void setAge(int age) {
  35. this.age = age;
  36. }
  37. public double getSalary() {
  38. return salary;
  39. }
  40. public void setSalary(double salary) {
  41. this.salary = salary;
  42. }
  43. @Override
  44. public String toString() {
  45. return "Employee{" +
  46. "name='" + name + '\'' +
  47. ", age=" + age +
  48. ", salary=" + salary +
  49. '}';
  50. }
  51. }

需求方法代码

  1. List<Employee> employees = Arrays.asList(
  2. new Employee("Fcant", 14, 99998.0),
  3. new Employee("Fcary", 10, 998.045),
  4. new Employee("Fcloi", 15, 934598.0),
  5. new Employee("Fcmop", 19, 56998.04),
  6. new Employee("Fcctr", 18, 945698.0),
  7. new Employee("Fcqyt", 17, 998.0645)
  8. );
  9. // 需求:获取当前公司中员工年龄大于17的员工的信息
  10. public List<Employee> filterEmployeeByAge(List<Employee> list) {
  11. List<Employee> employees = new ArrayList<>();
  12. for (Employee employee : list) {
  13. if (employee.getAge() > 16) {
  14. employees.add(employee);
  15. }
  16. }
  17. return employees;
  18. }
  19. // 需求:获取当前公司中员工薪水大于5000的员工的信息
  20. public List<Employee> filterEmployeeBySalary(List<Employee> list) {
  21. List<Employee> employees = new ArrayList<>();
  22. for (Employee employee : list) {
  23. if (employee.getSalary() > 5000) {
  24. employees.add(employee);
  25. }
  26. }
  27. return employees;
  28. }

对以上重复性需求进行优化处理

①、优化方式一:策略设计模式

将相差不大需求使用的统一接口来处理
  1. package com.fcant.java8.lambda.inter;
  2. /**
  3. * ExPredicate
  4. * <p>
  5. * encoding:UTF-8
  6. *
  7. * @author Fcant 下午 21:40:31 2020/2/18/0018
  8. */
  9. public interface ExPredicate<T> {
  10. public boolean is(T t);
  11. }

对接口进行不同的需求通过实现类来实现
  1. package com.fcant.java8.lambda.inter.impl;
  2. import com.fcant.java8.lambda.bean.Employee;
  3. import com.fcant.java8.lambda.inter.ExPredicate;
  4. /**
  5. * FilterEmployeeByAge
  6. * <p>
  7. * encoding:UTF-8
  8. *
  9. * @author Fcant 上午 10:28:35 2020/2/19/0019
  10. */
  11. public class FilterEmployeeByAge implements ExPredicate<Employee> {
  12. @Override
  13. public boolean is(Employee employee) {
  14. return employee.getAge() > 15;
  15. }
  16. }
  1. package com.fcant.java8.lambda.inter.impl;
  2. import com.fcant.java8.lambda.bean.Employee;
  3. import com.fcant.java8.lambda.inter.ExPredicate;
  4. /**
  5. * FilterEmployeeBySalary
  6. * <p>
  7. * encoding:UTF-8
  8. *
  9. * @author Fcant 上午 10:39:30 2020/2/19/0019
  10. */
  11. public class FilterEmployeeBySalary implements ExPredicate<Employee> {
  12. @Override
  13. public boolean is(Employee employee) {
  14. return employee.getSalary() > 5000;
  15. }
  16. }
  1. // 优化方式一:策略设计模式
  2. public List<Employee> filterEmployee(List<Employee> list, ExPredicate<Employee> exPredicate) {
  3. List<Employee> employees = new ArrayList<>();
  4. for (Employee employee : list) {
  5. if (exPredicate.is(employee)) {
  6. employees.add(employee);
  7. }
  8. }
  9. return employees;
  10. }
  11. @Test
  12. public void Test() {
  13. // List<Employee> employees = filterEmployeeByAge(this.employees);
  14. List<Employee> employeeListByAge = filterEmployee(this.employees, new FilterEmployeeByAge());
  15. for (Employee employee : employeeListByAge) {
  16. System.out.println(employee);
  17. }
  18. System.out.println("----------------根据薪资过滤---------------");
  19. List<Employee> employeeListBySalary = filterEmployee(this.employees, new FilterEmployeeBySalary());
  20. for (Employee employee : employeeListBySalary) {
  21. System.out.println(employee);
  22. }
  23. }

②、优化方式二:匿名内部类

  1. @Test
  2. public void Test() {
  3. // 优化方式二:匿名内部类
  4. System.out.println("-------------匿名内部类-------------");
  5. List<Employee> filterEmployee = filterEmployee(this.employees, new ExPredicate<Employee>() {
  6. @Override
  7. public boolean is(Employee employee) {
  8. return employee.getSalary() < 5000;
  9. }
  10. });
  11. for (Employee employee : filterEmployee) {
  12. System.out.println(employee);
  13. }
  14. }

③、优化方式三:Lambda表达式

  1. @Test
  2. public void Test() {
  3. // 优化方式三:Lambda表达式
  4. System.out.println("-------------Lambda表达式------------");
  5. List<Employee> employeesByLambda = filterEmployee(this.employees, (e) -> e.getSalary() <= 5000);
  6. employeesByLambda.forEach(System.out::println);
  7. }

④、优化方式四:Stream流

  1. @Test
  2. public void Test() {
  3. // 优化方式四:Stream流
  4. System.out.println("-----------------Stream流过滤---------------");
  5. employees.stream()
  6. .filter((e) -> e.getSalary() >= 5000)
  7. .limit(2)
  8. .forEach(System.out::println);
  9. employees.stream()
  10. .map(Employee::getName)
  11. .forEach(System.out::println);
  12. }

4、Lambda表达式的基础语法

Java8中引入了一个新的操作符”->”该操作符称为箭头操作符或者Lambda操作符,箭头操作符将Lambda表达式拆分成两部分

  • 1.左侧:Lambda表达式的参数列表
  • 2.右侧:Lambda表达式中所需执行的功能,即Lambda体

    A.语法格式一:无参数,无返回值

    1. () -> System.out.println("Hello Lambda!")
    1. // 1.无参数,无返回值
    2. @Test
    3. public void noParamNoReturnTest() {
    4. Runnable runnable = new Runnable() {
    5. @Override
    6. public void run() {
    7. System.out.println("Hello World");
    8. }
    9. };
    10. runnable.run();
    11. System.out.println("---------Lambda实现--------");
    12. Runnable r = () -> System.out.println("Hello Lambda!");
    13. r.run();
    14. }

    B.语法格式二:有一个参数,无返回值

    1. (x) -> System.out.println(x);
    1. // 2.有一个参数,无返回值
    2. @Test
    3. public void oneParamNoReturnTest() {
    4. Consumer<String> consumer = (x) -> System.out.println(x);
    5. consumer.accept("Fv");
    6. }

    C.语法格式三:有一个参数,无返回值时小括号可以省略不写

    1. x -> System.out.println(x);
    1. // 3.有一个参数,无返回值时可以省略括号
    2. @Test
    3. public void oneParamNoReturnLostTest() {
    4. Consumer<String> consumer = x -> System.out.println(x);
    5. consumer.accept("Fv");
    6. }

    D.语法格式四:有一个或多个参数,有返回值,有多条处理语句

    1. Comparator<Integer> comparator = (x, y) -> {
    2. System.out.println("函数式接口");
    3. return Integer.compare(x, y);
    4. };
    1. // 4.有一个或多个参数,有返回值,有多条处理语句
    2. @Test
    3. public void paramReturnTest() {
    4. Comparator<Integer> comparator = (x, y) -> {
    5. return Integer.compare(x, y);
    6. };
    7. System.out.println(comparator.compare(1, 12));
    8. }

    E.语法格式五:若Lambda体中只有一条语句,return和大括号都可以不写

    1. (x, y) -> Integer.compare(x, y);
    1. // 5.只有一条语句,return和大括号都可以不写
    2. @Test
    3. public void paramReturnLostTest() {
    4. Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
    5. System.out.println(comparator.compare(1, 12));
    6. }

    F.语法格式六:Lambda表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”

    1. (Integer x, Integer y) -> Integer.compare(x, y);
    1. // 6.Lambda表达式的参数列表的数据类型可以省略不写
    2. @Test
    3. public void paramTypeReturnLostTest() {
    4. Comparator<Integer> comparator = (Integer x, Integer y) -> Integer.compare(x, y);
    5. System.out.println(comparator.compare(1, 12));
    6. }

    5、Lambda案例

    案例A

    调用Collection.sort()方法,通过定制排序比较两个Employee(先按年龄比,年龄相同按姓名比,使用Lambda作为参数传递)
    1. @Test
    2. public void empTest() {
    3. Collections.sort(employees, (e1, e2) -> {
    4. if (e1.getAge() == e2.getAge()) {
    5. return e1.getName().compareTo(e2.getName());
    6. }else {
    7. return -Integer.compare(e1.getAge(), e2.getAge());
    8. }
    9. });
    10. employees.forEach(System.out::println);
    11. }

    案例B

    ①声明函数式接口,接口中声明抽象方法,public String getValue(String str);
    ②声明类TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值。
    ③再将一个字符串的第二个和第四个索引位置进行截取子串 ```java package com.fcant.java8.lambda.inter;

/**

  • Func
  • encoding:UTF-8 *
  • @author Fcant 下午 20:24:19 2020/2/19/0019 */ @FunctionalInterface public interface Func { String getValue(T t); } java // 案例二 // ①声明函数式接口,接口中声明抽象方法,public String getValue(String str); // ②声明类TestLambda,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值。 // ③再将一个字符串的第二个和第四个索引位置进行截取子串 @Test public void functionTest() { System.out.println(option(“agHdfC”, (x) -> x.toUpperCase())); System.out.println(option(“hsafdsfh”, (x) -> x.substring(2, 5))); }

public String option(String s, Func func) { return func.getValue(s); }

  1. <a name="vHNjA"></a>
  2. ## 案例C
  3. ①声明一个带两个泛型的函数式接口,泛型类型为`<T, R>`,`T`为参数,`R`为返回值<br />②接口中声明对应的抽象方法<br />③在TestLambda类中声明方法,使用接口作为参数,计算两个long型参数的和<br />④再计算两个long型参数的乘积
  4. ```java
  5. package com.fcant.java8.lambda.inter;
  6. /**
  7. * Funca
  8. * <p>
  9. * encoding:UTF-8
  10. *
  11. * @author Fcant 下午 20:32:15 2020/2/19/0019
  12. */
  13. @FunctionalInterface
  14. public interface Funca<T, R> {
  15. R op(T t1, T t2);
  16. }
  1. // 案例三
  2. // ①声明一个带两个泛型的函数式接口,泛型类型为<T, R>,T为参数,R为返回值
  3. // ②接口中声明对应的抽象方法
  4. // ③在TestLambda类中声明方法,使用接口作为参数,计算两个long型参数的和
  5. // ④再计算两个long型参数的乘积
  6. @Test
  7. public void trTest() {
  8. System.out.println(ops(12l, 56l, (t1, t2) -> t1 + t2));
  9. System.out.println(ops(12l, 56l, (t1, t2) -> t1 * t2));
  10. }
  11. public Long ops(Long t1, Long t2, Funca<Long, Long> funca) {
  12. return funca.op(t1, t2);
  13. }