zoukankan      html  css  js  c++  java
  • java8之lambda表达式&方法引用(一)

    本文将简单的介绍一下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接口.这就是一个标准的函数式接口


    测试一下我们写的过滤方法,分别按照工资和工作名称进行过滤
    1 List<Emp> filterSalEmp = empService.filter1(listEmp, Emp emp -> emp.getSal() > 3000);
    2 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  可以不带参数类型.写成下面这样

    1 List<Emp> filterSalEmp = empService.filter1(listEmp, emp -> emp.getSal() > 3000);
    2 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表达式和方法引用就简单的介绍到这里,

    限于篇幅有些地方介绍的不是很详细,如果有疑问欢迎大家随时提问.

  • 相关阅读:
    Eclipse在线安装spring-tool-suit插件
    使用Eclipse构建Maven项目
    uwsgi+flask环境中安装matplotlib
    开启flask调试
    linux进入软连接所指向的原目录
    eclipse打不开,报错 "java was started with exit code=13"
    gnuplot 的安装
    使用tcp_probe时最初没有输出,先卸载后加载模块之后就有了。
    一个简单的socket程序运行与抓包查看
    如何查看文件是dos格式还是unix格式的?
  • 原文地址:https://www.cnblogs.com/xisuo/p/9705944.html
Copyright © 2011-2022 走看看