重学Java 8新特性 | 第3讲——我们为什么要使用Lambda表达式?

Posted 李阿昀

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了重学Java 8新特性 | 第3讲——我们为什么要使用Lambda表达式?相关的知识,希望对你有一定的参考价值。

接下来,我们就正式来开始学习Java 8了。

上一讲中,我就已经讲过了,Java 8新特性中最为核心的便是Lambda表达式与Stream API,只不过这一讲我首先会为大家讲解Lambda表达式。其实,Lambda表达式就是Java 8提出的一种新的语法格式。在讲这个语法格式之前,我们首先得了解一下为什么要用Lambda表达式。

为什么要使用Lambda表达式?

首先,新建一个普通的Java项目,例如java8-day01,不用我说,相信大家应该都知道现在得用Java 8。

然后,在项目下创建一个包,例如com.meimeixia.java8,接着在该包下创建一个类,不妨就叫做TestLambda。

以上准备工作做好之后,接下来我就来说说到底我们为什么要用Lambda表达式?Lambda表达式到底有什么好?

要想知道答案,首先我们得弄清Lambda表达式解决的是什么问题。大家先回顾回顾原来的匿名内部类,匿名内部类是个什么东东,大家应该还记得吧!就拿如下定义的这样一个比较器来说,它是不是用于比较两个Integer的大小的啊!

package com.meimeixia.java8;

import org.junit.Test;

import java.util.*;

/**
 * @author liayun
 * @create 2021-12-08 20:47
 */
public class TestLambda 

    // 原来的匿名内部类
    @Test
    public void test1() 
        // 定义一个比较器,用于比较两个Integer的大小
        Comparator<Integer> com = new Comparator<Integer>() 
            @Override
            public int compare(Integer o1, Integer o2) 
                return Integer.compare(o1, o2);
            
        ;

        // 使用以上定义好的比较器作为参数进行一个传递
        TreeSet<Integer> ts = new TreeSet<Integer>(com);
    


以上是不是通过匿名内部类实现Comparator接口的方式来定义了一个比较器啊!定义好之后,参照上面的代码,我们是不是将该匿名内部类的实例作为参数传递给了TreeSet呀!大家现在对于匿名内部类应该回过味来了吧!

回过味来之后,大家再好好看一下以上匿名内部类的代码,实际上以上匿名内部类有用的代码说白了就下面这一句,即:

return Integer.compare(o1, o2);

上下其他那些代码都是支持它的代码,说白了就是都没啥用。可见,我们就为了这一句有用的核心的代码,是不还得多写几行代码啊!抱怨也没啥用,因为匿名内部类本来就这样。还记得当初你学匿名内部类的样子吧,学的时候是不挺痛苦的啊,相信你应该吐槽过,这尼玛写的都是什么玩意啊,乱七八糟的,可惜匿名内部类就长这样,你无法改变事实,只能被迫自己去适应。

但是,现在在Java 8中自从有了Lambda表达式之后,就能解决匿名内部类代码臃肿的问题了,即使用Lambda表达式可以简化上面编写的匿名内部类。

package com.meimeixia.java8;

import org.junit.Test;

import java.util.*;

/**
 * @author liayun
 * @create 2021-12-08 20:47
 */
public class TestLambda 

    // 原来的匿名内部类
    @Test
    public void test1() 
        // 定义一个比较器,用于比较两个Integer的大小
        Comparator<Integer> com = new Comparator<Integer>() 
            @Override
            public int compare(Integer o1, Integer o2) 
                return Integer.compare(o1, o2); // 有用的核心的代码就这一句
            
        ;

        // 使用以上定义好的比较器作为参数进行一个传递
        TreeSet<Integer> ts = new TreeSet<Integer>(com);
    

    // 使用Lamdba表达式就可以简化上面编写的匿名内部类
    @Test
    public void test2() 
        // 把核心有用的代码(即Integer.compare(x, y))给提取出来,以作为一个实现
        Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
        TreeSet<Integer> ts = new TreeSet<Integer>(com); // 将比较器作为参数进行一个传递
    


看到了吧!以前匿名内部类我们还得写一大堆的代码,现在可好了,我们只须将核心有用的代码提取出来作为接口的实现即可,从上可知,现在只需要一句代码就搞定了,代码是不是简化了很多啊,那这就是使用Lambda表达式的好处。

当然,有童鞋可能会说,(x, y) -> Integer.compare(x, y)这他妈写的都是些什么玩意啊,嘻嘻😊,大家现在先暂时不用管它是什么,以后我会一点一点教大家的。

你就说,现在感觉它好不好吧?有的童鞋可能会说,我也没啥感觉啊,以前用匿名内部类我顶多也就多写3、4行代码,虽然现在只须写一句代码就能搞定了,但是我也不觉得多费劲啊,多写几行代码怕什么,我不能为了少写些代码就多学一门新的语法吧,哼😕,我不愿意!

好吧,如果以上案例还不能说服你的话,下面我们再来看一个案例。

现在有这样一个需求:假设某公司有若干雇员,每一个雇员都有其姓名、年龄以及工资等基本信息,现在想要获取当前公司中员工年龄不小于35的员工信息。类似于这种查询操作,相信大家以后在实际开发中会经常遇到,大家不妨试着思考一下该如何解决该需求?

为了解决该需求,首先我们应该创建一个雇员实体类,即Employee,如下所示。

package com.meimeixia.java8;

/**
 * @author liayun
 * @create 2021-12-08 20:52
 */
public class Employee 

    private int id;
    private String name;
    private int age;
    private double salary;

    public Employee() 

    

    public Employee(int id, String name, int age, double salary) 
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
    

    public int getId() 
        return id;
    

    public void setId(int id) 
        this.id = id;
    

    public String getName() 
        return name;
    

    public void setName(String name) 
        this.name = name;
    

    public int getAge() 
        return age;
    

    public void setAge(int age) 
        this.age = age;
    

    public double getSalary() 
        return salary;
    

    public void setSalary(double salary) 
        this.salary = salary;
    

    @Override
    public String toString() 
        return "Employee" +
                "id=" + id +
                ", name='" + name + '\\'' +
                ", age=" + age +
                ", salary=" + salary +
                '';
    
    

然后,整一个集合,让其里面存储的都是Employee实体类的对象。整完之后,编写一个方法用于获取当前公司中员工年龄不小于35的员工信息,接着,再做个测试,看是否真能获取到当前公司中员工年龄不小于35的员工信息。

package com.meimeixia.java8;

import org.junit.Test;

import java.util.*;

/**
 * @author liayun
 * @create 2021-12-08 20:47
 */
public class TestLambda 

    // 原来的匿名内部类
    @Test
    public void test1() 
        // 定义一个比较器,用于比较两个Integer的大小
        Comparator<Integer> com = new Comparator<Integer>() 
            @Override
            public int compare(Integer o1, Integer o2) 
                return Integer.compare(o1, o2); // 有用的核心的代码就这一句
            
        ;

        // 使用以上定义好的比较器作为参数进行一个传递
        TreeSet<Integer> ts = new TreeSet<Integer>(com);
    

    // 使用Lamdba表达式就可以简化上面编写的匿名内部类
    @Test
    public void test2() 
        // 把核心代码(即Integer.compare(x, y))给提取出来,以作为一个实现
        Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
        TreeSet<Integer> ts = new TreeSet<Integer>(com); // 将比较器作为参数进行一个传递
    

    List<Employee> employees = Arrays.asList(
            new Employee(100, "张三", 18, 9999.99),
            new Employee(101, "李四", 38, 5555.99),
            new Employee(102, "王五", 50, 6666.66),
            new Employee(103, "赵六", 16, 3333.33),
            new Employee(104, "田七", 8, 7777.77)
    );

    // 测试是否能获取到当前公司中员工年龄不小于35的员工信息?
    @Test
    public void test3() 
        List<Employee> list = filterEmployees(this.employees);

        for (Employee employee : list) 
            System.out.println(employee);
        
    

    // 需求:获取当前公司中员工年龄不小于35的员工信息
    public List<Employee> filterEmployees(List<Employee> list) 
        List<Employee> emps = new ArrayList<Employee>();

        for (Employee emp : list) 
            if (emp.getAge() >= 35) 
                emps.add(emp);
            
        

        return emps;
    


此时,运行以上test3方法,发现确实获取到了当前公司中员工年龄不小于35的员工信息,如下图所示。

这个时候,万一需求变了呢?现在还要获取当前公司中员工工资大于5000的员工信息。这操作是不很常见,大家逛淘宝的时候,搜索自己想要的商品的时候,是不是按照商品分类或者价格过滤过啊?也就是说,现在我们不仅要按年龄进行过滤,而且还要按工资进行过滤。

因此,我们还得在TestLambda类中编写一个用于获取当前公司中员工工资大于5000的员工信息的方法。

// 需求:获取当前公司中员工工资大于5000的员工信息
public List<Employee> filterEmployees2(List<Employee> list) 
    List<Employee> emps = new ArrayList<Employee>();

    for (Employee emp : list) 
        if (emp.getSalary() > 5000) 
            emps.add(emp);
        
    

    return emps;

不知你有没有发现上面根据需求编写的两个方法是不有大量的冗余代码啊,除了if判断语句之外,其他的几乎都相同。所以,一旦遇到这种大量的冗余代码,我们就得想办法来进行解决了,即需要进行优化处理。

大家想一下,原来我们对这种大量的冗余代码最好的优化方式是什么啊?是不是就是使用设计模式啊,相信大家应该学过很多种设计模式,要是没学过也没关系,看我写的「从零开始学习Java设计模式」专栏即可,写得还不错哟😂

但要用哪一种设计模式进行代码优化呢?这里可以使用策略设计模式,那什么叫策略设计模式呢?通俗一点来说,就是你给它什么策略,它就按什么策略进行过滤。

怎么使用策略设计模式来重构代码呢?既然我们发现只有判断条件不一致,那么我们便可以定义一个接口来进行条件判断。

package com.meimeixia.java8;

/**
 * @author liayun
 * @create 2021-12-08 21:31
 */
public interface MyPredicate<T> 

    public boolean test(T t);


如果我们要定义新的判断规则,那么只需要定义一个类实现以上MyPredicate接口即可。例如,我们要进行员工年龄不小于35的判断,实现类就应该这样写。

package com.meimeixia.java8;

/**
 * @author liayun
 * @create 2021-12-08 21:32
 */
public class FilterEmployeeByAge implements MyPredicate<Employee> 
    @Override
    public boolean test(Employee t) 
        return t.getAge() >= 35;
    

或者,进行员工工资大于5000的判断。

package com.meimeixia.java8;

/**
 * @author liayun
 * @create 2021-12-08 23:58
 */
public class FilterEmployeeBySalary implements MyPredicate<Employee> 
    @Override
    public boolean test(Employee t) 
        return t.getSalary() > 5000;
    

有了以上两个实现类之后,现在我们就只需要编写如下这样一个filterEmployee方法了,而再也不用费心费力去为每一种条件变化编写一个相应的方法了。

/**
    * 现在只需要这一个方法了,再也不用费心费力为每一种条件变化编写一个相应的方法了!
    * @param list:传递进来的集合
    * @param mp:对集合按照MyPredicate里面指定的方式进行过滤
    * @return
    */
public List<Employee> filterEmployee(List<Employee> list, MyPredicate<Employee> mp) 
    List<Employee> emps = new ArrayList<Employee>();

    for (Employee employee : list) 
        if (mp.test(employee)) 
            emps.add(employee);
        
    

    return emps;

然后,我们就得对以上编写的方法进行测试了。怎么进行测试呢?很简单,在TestLambda类中编写一个如下test4方法即可。

// 优化方式一:策略设计模式
@Test
public void test4() 
    List<Employee> list = filterEmployee(employees, new FilterEmployeeByAge());
    for (Employee employee : list) 
        System.out.println(employee);
    

    System.out.println("-------------------------------");

    List<Employee> list2 = filterEmployee(this.employees, new FilterEmployeeBySalary());
    for (Employee employee : list2) 
        System.out.println(employee);
    

此时,运行以上test4方法,结果如下图所示,可知现在确实获取到了当前公司中员工年龄不小于35以及员工工资大于5000的员工信息。

现在大家该知道使用策略设计模式优化之后的好处了吧!是不只需要写一个方法(即filterEmployee方法)就行了啊,以前我们还得根据每一种条件变化去编写一个对应的方法,如此一来必然就会导致代码冗余,而这种现象是我们不愿意看到的。

虽说使用策略设计模式来重构了代码,但是依然还有一点不好的地方,即为每个判断规则都定义一个实现类的话有点多余。

于是,我们就有了第二种优化方式,即直接使用匿名内部类来实现接口。使用这种优化方式,我们不妨来获取一下当前公司中员工工资不大于5000的员工信息。

// 优化方式二,直接使用匿名内部类来实现接口。例如,获取当前公司中员工工资不大于5000的员工信息
@Test
public void test5() 
    List<Employee> list = filterEmployee(this.employees, new MyPredicate<Employee>() 
        @Override
        public boolean test(Employee t) 
            return t.getSalary() <= 5000;
        
    );

    for (Employee employee : list) 
        System.out.println(employee);
    

此时,运行以上test5方法,结果如下图所示,发现确实获取到了当前公司中员工工资不大于5000的员工信息。

但是,在一开始我们就已经说过了,以上匿名内部类里面有用的代码就return t.getSalary() <= 5000;这一句,而我们为了这一句是不多写了5~6行代码啊,可读性一下子就降低了,结构也乱了,代码根本就不够简洁。除非你是那种很早期的程序员,那时候的程序员按代码的行数算钱,比如一行五毛,那样的话,你就得像上面那样写了。

你不禁就要问了,既然以上代码的可读性比较差,那么有没有另外一种优化方式呢?有,匿名内部类可以使用Lambda表达式来代替,以此我们就能简化代码了。

// 优化方式三:Lambda表达式。例如获取当前公司中员工工资大于5000的员工信息
@Test
public void test6() 
    // 这里依然还是要使用上面编写的filterEmployee方法
    // List<Employee> list = filterEmployee(this.employees, (e) -> e.getSalary() <= 5000);
    List<Employee> list = filterEmployee(this.employees, (e) -> e.getSalary() > 5000);
    // 遍历集合
    list.forEach(System.out::println);

此时,运行以上test6方法,结果如下图所示,发现确实获取到了当前公司中员工工资大于5000的员工信息。

以上写的代码够简单吧!两行代码就能搞定了。现在大家该对Lambda表达式有所体会了吧!是不感觉还挺好啊!有想学的欲望了吧!

如果你对以上优化方式还不满意,那也没关系,因为我们还可以使用Stream API来继续优化代码。注意,如果使用Stream API来优化代码,那么除了在TestLambda类中定义好的employees集合,以及Employee实体类之外,剩下的什么都可以没有。使用这种优化方式,我们不妨来获取一下当前公司中员工工资大于5000的员工信息。

// 优化方式四:Stream API
@Test
public void test7() 
    employees.stream()
             .filter((e) -> e.getSalary() > 5000)
             .forEach(System.out::println);

此时,运行以上test7方法,结果如下图所示,发现确实获取到了当前公司中员工工资大于5000的员工信息。

如果这时我们还想在查询出来的当前公司中员工工资大于5000的员工信息的基础之上取出排名最前的两个员工信息,那么该怎么办呢?很简单,像下面这样做就行了。

// 优化方式四:Stream API
@Test
public void test7() 
    employees.stream()
             .filter((e) -> e.getSalary() > 5000)
             .limit(2)
             .forEach(System.out::println);

再次运行以上test7方法,结果如下图所示,发现确实是获取到了。

重学Java 8新特性 | 第3讲——我们为什么要使用Lambda表达式?

重学Java 8新特性 | 第1讲——我们为什么要学习Java 8新特性?

重学Java 8新特性 | 第1讲——我们为什么要学习Java 8新特性?

重学Java 8新特性 | 第2讲——Java 8新特性简介

重学Java 8新特性 | 第6讲——方法引用与构造器引用

重学Java 8新特性 | 第5讲——函数式接口