本文将简单的介绍一下 Lambda 表达式和方法引用, 这也是 Java8 的重要更新, Lambda 表达式和方法引用最主要的功能是为流 (专门负责迭代数据的集合) 服务.
什么是 lambda 表达式
可以把 lambda 表达式理解为简洁的匿名函数.
我们先声明一个函数式接口(函数式接口: 就是只有一个抽象方法的接口. lambda 表达式和方法引用, 只能用在函数式接口上), 比较一下 lambda 表达式和匿名函数
- public interface Animal {
- void cry();
- public static void main(String [] args){
- Animal dog = new Animal() {
- @Override
- public void cry() {
- System.out.println("狗: 汪汪叫");
- }
- };
- dog.cry();
- Animal cat = () -> System.out.println("猫: 喵喵叫");
- cat.cry();
- }
- }
一个 Animal 的接口, 里面只有一个 cry()的抽象方法, 分别用匿名函数和 lambda 表达式去实现这个接口. 使用 lambda 表达式的方法非常的简洁, 只需要一行.
lambda 表达式语法: 参数 -> 具体的实现.
函数式接口的方法叫做函数描述符, lambda 表达式的参数和实现必须和函数描述符的参数和返回值一一对应. cry()方法的参数和返回值都没有所以 lambda 表达式就是() -> System.out.println("猫: 喵喵叫");
如果实现有多条语句的话, 要写在 {} 中, 并且以; 结尾.
- () -> {
- xxx;
- yyy;
- return "ccc";
- }
列举一个高端一点的使用 lambda 表达式的方法. 以 Oracle 的 Emp(员工表)为例.
表结构
- public class Emp {
- private BigDecimal empno;
- private String ename;
- private String job;
- private BigDecimal mgr;
- private Date hiredate;
- private Double sal;
- private BigDecimal comm;
- private BigDecimal deptno;
- public BigDecimal getEmpno() {
- return empno;
- }
- public void setEmpno(BigDecimal empno) {
- this.empno = empno;
- }
- public String getEname() {
- return ename;
- }
- public void setEname(String ename) {
- this.ename = ename == null ? null : ename.trim();
- }
- public String getJob() {
- return job;
- }
- public void setJob(String job) {
- this.job = job == null ? null : job.trim();
- }
- public BigDecimal getMgr() {
- return mgr;
- }
- public void setMgr(BigDecimal mgr) {
- this.mgr = mgr;
- }
- public Date getHiredate() {
- return hiredate;
- }
- public void setHiredate(Date hiredate) {
- this.hiredate = hiredate;
- }
- public Double getSal() {
- return sal;
- }
- public void setSal(Double sal) {
- this.sal = sal;
- }
- public BigDecimal getComm() {
- return comm;
- }
- public void setComm(BigDecimal comm) {
- this.comm = comm;
- }
- public BigDecimal getDeptno() {
- return deptno;
- }
- public void setDeptno(BigDecimal deptno) {
- this.deptno = deptno;
- }
- }
现在我们要写一个方法, 过滤所有工资在 3000 以上的员工(可能有的人可能会想, 我直接写 sql 不得了, 费这么多劲干什么, 所以我们以下的测试都假设数据是从 Redis 查询出来的. 需要手动写过滤条件)
- public List<Emp> filter(List<Emp> listEmp){
- List<Emp> filterList = new ArrayList<>();
- for (Emp emp :listEmp) {
- if (emp.getSal()>3000){
- filterList.add(emp);
- }
- }
- return filterList;
- }
这么写的坏处是条件硬编码, 如果光是改工资, 我们可以把 3000 抽取为一个参数, 但是如果要将条件改为小于呢, 如果过滤的是员工的工作呢. 可能新手就会进行复制粘贴改一改条件, 但是当重复的代码达到一定的数量时, 维护起来就是个灾难.
我们看看 Java8 提供的函数式编程, 可以怎么解决这个方法.(当然使用匿名函数也可以, 但是不够简洁).
把变化的的条件抽取出去, 变为一个参数 Predicate. 具体的实现就是实现这个接口的 test 方法.
- public List<Emp> filter1(List<Emp> listEmp, Predicate<Emp> predicate){
- List<Emp> filterList = new ArrayList<>();
- for (Emp emp :listEmp) {
- if (predicate.test(emp)){
- filterList.add(emp);
- }
- }
- return filterList;
- }
我们利用了 java.util.function 这个包提供的 Predicate 接口. 这就是一个标准的函数式接口
测试一下我们写的过滤方法, 分别按照工资和工作名称进行过滤
- List<Emp> filterSalEmp = empService.filter1(listEmp, Emp emp -> emp.getSal()> 3000);
- List<Emp> filterJobEmp = empService.filter1(listEmp, Emp emp -> "SALMAN".equals(emp.getJob()));
Predicate 接口的方法 boolean test(T t); 返回值是 Boolean 类型的, 参数是任意类型 我们的实现 Emp emp -> emp.getSal()> 3000 参数 Emp , 返回 Boolean 类型的值 emp.getSal()> 3000 完全满足. 可以看到使用函数式接口编程提高了代码的灵活性和可重用性.
其实 lambda 表达式的类型是可以从上下文中自己推断出来的, 也就是说 上面的 lambda 的参数 Emp emp 可以不带参数类型. 写成下面这样
- List<Emp> filterSalEmp = empService.filter1(listEmp, emp -> emp.getSal()> 3000);
- List<Emp> filterJobEmp = empService.filter1(listEmp, emp -> "SALMAN".equals(emp.getJob()));
lambda 表达式使用局部变量
回到之前的例子:
- String catCry = "猫: 喵喵叫";
- Animal cat = () -> System.out.println(catCry);
- cat.cry();
打印输出: 猫: 喵喵叫
lambda 表达式可以使用局部变量, 但是必须是 final 类型的或事实上 final 类型的(不可改变).
<<java8 实战>>中的解释:
第一, 实例变量和局部变量背后的实现有一
个关键不同. 实例变量都存储在堆中, 而局部变量则保存在栈上. 如果 Lambda 可以直接访问局
部变量, 而且 Lambda 是在一个线程中使用的, 则使用 Lambda 的线程, 可能会在分配该变量的线
程将这个变量收回之后, 去访问该变量. 因此, Java 在访问自由局部变量时, 实际上是在访问它
的副本, 而不是访问原始变量. 如果局部变量仅仅赋值一次那就没有什么区别了 -- 因此就有了
这个限制.
第二, 这一限制不鼓励你使用改变外部变量的典型命令式编程模式(我们会在以后的各章中
解释, 这种模式会阻碍很容易做到的并行处理).
方法引用
方法引用可以理解为 lambda 表达式的快捷写法, 它比 lambda 表达式更加的简洁, 可读性更高. 有更好的重用性. 如果实现比较简单, 一句话就可以实现, 复用的地方又不多推荐使用 lambda 表达式, 否则应该使用方法引用.
方法引用的格式 类名:: 方法名
我们使用方法引用的方式, 重新实现上面刚刚过滤员工表的例子.
定义两个条件类, 方法的参数和返回值定义的和 predicate 的函数名描述符一致
- public class EmpConditionA {
- public static boolean test(Emp emp) {
- return emp.getSal()> 3000;
- }
- }
- public class EmpConditionB{
- public static boolean test(Emp emp) {
- return "engineer".equals(emp.getJob());
- }
- }
实现方式: 使用类名:: 方法的方式
- List<Emp> listEmp = empService.listEmp();
- List<Emp> filterSalEmp = empService.filter1(listEmp, EmpConditionA::test);
- List<Emp> filterJobEmp = empService.filter1(listEmp, EmpConditionB::test);
因为这个方法调用的是第三方类的方法所以是 static 的
还有两种调用方式: 一种是直接调用流中的实例的方式, 还有一种是调用局部变量的方式.
直接调用流中的实例的方式: 注意下面的 Emp::getJob 就相当于集合中每一个 emp 对象都调用自己的 getJob 方法.
这个例子是讲将集合转换为流, map()方法可以理解为对集合的每一个元素进行相应的操作, 这里就是对每一个 emp 实例调用 getJob 方法. 最后. collect(Collectors.toList())将流转换为新的 list 集合(关于流, 笔者后面会继续更新相关的博客).
listEmp.stream().map(Emp::getJob).collect(Collectors.toList());
调用局部变量的方式: 创建条件 EmpconditionA 的实例
- EmpConditionA empConditionA = new EmpConditionA();
- List<Emp> filterSalEmp = empService.filter1(listEmp, empConditionA::test);
好了关于 lambda 表达式和方法引用就简单的介绍到这里,
限于篇幅有些地方介绍的不是很详细, 如果有疑问欢迎大家随时提问.
来源: https://www.cnblogs.com/xisuo/p/9705944.html