lambda表达式

lambda表达式

其实lambda表达式的本质是接口的子实现
lambda表达式简化了匿名内部类的写法
lambda表达式另一个叫法,函数式编程

1.lambda表达式:

lambda表达式可以称之为闭包,他是java8中的一个非常重要的特性
很多java8新特性的写法都会用到lambda表达式
lambda表达式允许函数作为一个方法的参数,函数作为参数传递到方法中,这样代码就会变得简洁

语法:

lambda 操作符 ->
-> 左侧: 是 lambda 表达式的参数列表(接口中抽象方法的参数列表)
-> 右侧: 是 lambda 表达式中所需执行的功能,即 lambda 体(接口中抽象方法实现的功能)

1
2
3
(parameters)->expression表达式

(parameters)->{statement代码块;}

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

比如:
//不要参数,返回值为5
()->5
//接受一个参数(推断出数字类型),返回参数的2倍
x->2*x
//接受两个参数(推断出数字类型),返回值为参数差值
(x,y)->x-y
//接受两个参数,返回值为x-y
(int x,int y)->x-y
//接受一个参数,类型为String的参数,并输出参数的值
//推断出没有返回值,相当于方法的返回值void
(String str)->System.out.println(str)
//接受两个参数(推断出数字类型,返回值为参数的差值)
(x,y)->{return (x-y);}

其实lambda表达式的本质就是接口的子实现
lambda表达式简化了匿名内部类的写法
lambda表达式另一个写法叫做函数式编程
注意:
就是把接口的实现是内部类,转换成函数式写法

使用lambda表达式需要注意变量的作用域问题:

  • lambda表达式引用外层的成员变量
    如果外层的成员变量为final的,则在lambda内部不能修改成员变量的值
    如果外层的成员变量不是final的,则在lambda内部能修改成员变量的值
  • lambda表示式的局部变量可以不用声明为final,但必须不可以被后面的代码修改(即隐式的final特性)
  • lambda表达式不允许声明一个与局部变量同名的参数或局部变量

2.方法的引用:

方法的引用方式用类名::方法名,其本质还是lambda表达式

具体的写法有四种

-构造器的引用 语法Class::new
-静态方法的引用 语法 Class::静态方法的名称
-特定类任意的对象引用 语法 Class::非静态方法名称
-特定对象的方法引用 语法 instance(实例)对象::非静态的方法的的名称

1
2
3
4
5
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable{...}

public interface List<E> extends Collection<E> {...}

public interface Collection<E> extends Iterable<E> {...}

在 Iterable 接口的源码中提供了方法 forEach(Consumer<? super T> action) ,该方法的参数为一个接口 Consumer 对象, 并且调用其 accept(t) 方法。

1
2
3
4
5
6
7
8
9
10
11
12
//Iterable接口的源码
public interface Iterable<T> {

......
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
......
}

在 Consumer 接口源码中,有一个 accept(T t) 方法。而且该接口用 @FunctionalInterface 修饰,是一个函数式接口

1
2
3
4
5
6
7
8
9
10
11
12
//Consumer接口源码
@FunctionalInterface
public interface Consumer<T> {

/**
* Performs this operation on the given argument.
*
* @param t the input argument
*/
void accept(T t);
......
}

再结合案例代码,不难分析

3.函数式接口

lambda 表达式需要”函数式接口”的支持
接口中只有一个抽象方法的接口,称为函数式接口,可以使用注解 @FunctionalInterface 修饰 (保证该接口中只有一个抽象方法),可以检查是否是函数式接口。

lambda 练习

  1. 调用 Collections.sort() 方法,通过定制排序比较两个 Employee (先按年龄比,年龄相同按姓名比), 使用 lambda 作为参数传递。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    package lambda;

    import java.util.ArrayList;
    import java.util.Collections;
    /**
    * @author gavino
    */
    public class T_1 {

    public static void main(String[] args) {
    ArrayList<Employee> emps = new ArrayList<Employee>();
    emps.add(new Employee("张三",19));
    emps.add(new Employee("李四",18));
    emps.add(new Employee("赵六",19));
    emps.add(new Employee("王五",20));
    Collections.sort(emps,(e1,e2)->{
    if (e1.getAge()==e2.getAge()) {
    return e1.getName().compareTo(e2.getName());
    }else {
    return Integer.compare(e1.getAge(), e2.getAge());
    }
    });
    emps.forEach(emp -> System.out.println(emp));
    }
    }

    class Employee{
    private String name;
    private Integer age;

    public Employee() {
    }
    public Employee(String name, Integer age) {
    this.name = name;
    this.age = age;
    }
    public String getName() {
    return name;
    }
    public Integer getAge() {
    return age;
    }

    @Override
    public String toString() {
    return "Employee [name=" + name + ", age=" + age + "]";
    }
    }

结果

1
2
3
4
Employee [name=李四, age=18]
Employee [name=张三, age=19]
Employee [name=赵六, age=19]
Employee [name=王五, age=20]
  1. ① 声明函数式接口,接口中声明抽象方法,public string getValue(String str);
    ② 声明类 TestLambda ,类中编写方法使用接口作为参数,将一个字符串转换成大写,并作为方法的返回值
    ③ 再将字符串的第 2 个和第 4 个索引位置进行截取子串

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    package lanmbda;

    public class T_2 {
    public static void main(String[] args) {
    String str = "lanzhou";
    String subStr = strHandler(str,(s)->s.substring(2, 4));
    System.out.println(subStr);
    String upperStr = strHandler(str,(s)->s.toUpperCase());
    System.out.println(upperStr);
    }
    /**
    * 用于处理字符串
    * @return
    */
    public static String strHandler(String str, MyFunction mf) {
    return mf.getValue(str);
    }
    }

    /**
    * 函数式接口
    * @author gavino
    */
    @FunctionalInterface
    interface MyFunction{
    public String getValue(String str);
    }
  2. ① 声明一个带两个泛型的函数式接口,泛型类型为 <T,R> T 为参数,R 为返回值
    ② 接口中声明对应抽象方法
    ③ 在 TestLambda 类中声明方法,使用接口作为参数,计算两个 long 型参数的和
    ④ 再计算两个 long 型参数的乘积

4.四大内置核心函数式接口

在 java 8 之前已经有部分的函数式接口
比如

java 8 以后

评论