Java8-Lambda:初识Lambda

又是热爱学习的一天!!
在这里插入图片描述在这里插入图片描述
Java8新增的 Lambda 表达式虽然看着很NB,但其实 Lambda 表达式的本质只是一个"语法糖"而已。
这篇文章只是初识 Lambda,先看看它的好处是什么,它能给我们带来什么样的体验,至于 Lambda 具体怎么用以及什么时候用,会在后面的文章陆续学习到。

都说 Lambda 表达式能够优化内部类,我们先来看看它优化后是怎么样的。
我们以前要比较两个 Integer 大小的时候,是怎样实现的呢?是不是通过下面这种内部类的方式实现的:

    /**
     * 匿名内部类实现 Comparator
     */
    @Test
    public void compareTest(){
        Comparator<Integer> com = new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        };
        System.out.println(com.compare(100, 20));
    }

匿名内部类看起来有点杂乱,而使用 Lambda 优化后是什么样子呢?正如下所示:

    /**
     * 使用 Lambda 表达式优化匿名内部类实现 Comparator
     */
    @Test
    public void compareLambdaTest(){
        Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
        System.out.println(com.compare(100, 20));
    }

使用 Lambda 表达式之后将原来的6行代码,变成了一行代码实现,看起来确实很舒服了很多。

如果觉得这个简单的例子,还不足以说服自己,那么接下来就把 Lambda 带到需求中去,看看以前的实现方式和 Lambda 的实现方式有何不同吧。

简单需求

学校中有一群学生,我要通过年龄或者学号来过滤学生。

看到这个需求的第一反映是不是得有一个学生类,里面有一些学生的属性,那么下面就创建一个叫做 Student 的学生类:

public class Student {

    private String name;
    private int age;
    private int studentID;

	...省略构造器
	...省略 get/set 方法
	...省略 toString 方法
}

有了学生类之后,我们还需要模拟一群学生,把他们用 List 装起来。

    List<Student> students = Arrays.asList(
            new Student("小王", 10, 1006),
            new Student("小李", 11, 1005),
            new Student("小陈", 12, 1004),
            new Student("小明", 13, 1003),
            new Student("小红", 14, 1002),
            new Student("小张", 15, 1001)
    );

好啦,数据已经准备就绪,接下来看具体的需求

需求一:过滤年龄大于12的学生

按照以前最原始的方法,我们是怎么实现的呢?
是不是得先有一个方法,然后把 students 一群学生作为参数传进去,然后再一顿过滤,最后返回符合条件的学生集合就行了。

    /**
     * 根据年龄过滤学生
     * @param list 所有学生的集合
     * @return 过滤后符合条件的学生集合
     */
    public List<Student> filterStudentByAge(List<Student> list){
        List<Student> students = new ArrayList<>();
        for (Student stu : list) {
            if(stu.getAge() > 12){
                students.add(stu);
            }
        }
        return students;
    }

实现完了之后,再来调用看看结果:

    @Test
    public void getStudentByAge(){
        List<Student> list = filterStudentByAge(students);
        for (Student stu : list) {
            System.out.println(stu.toString());
        }
    }

执行结果如下:

Student{name='小明', age=13, studentID=1003}
Student{name='小红', age=14, studentID=1002}
Student{name='小张', age=15, studentID=1001}

结果符合我们的预期,我们将年龄大于12的学生过滤出来了。
假设现在我们的需求变化了。

需求二:过滤出学号大于1003的学生

那么还是按照以前最原始的方法,我们是怎么实现的呢?是不是得先有一个方法,然后把 students 一群学生作为参数传进去,然后再一顿过滤,最后返回符合条件的学生集合就行了。

    /**
     * 根据学号过滤学生
     * @param list 所有学生的集合
     * @return 过滤后符合条件的学生集合
     */
    public List<Student> filterStudentByID(List<Student> list){
        List<Student> students = new ArrayList<>();
        for (Student stu : list) {
            if(stu.getStudentID() > 1003){
                students.add(stu);
            }
        }
        return students;
    }

实现完了之后,再来调用看看结果:

    @Test
    public void getStudentByID(){
        List<Student> list = filterStudentByID(students);
        for (Student stu : list) {
            System.out.println(stu.toString());
        }
    }

执行结果如下:

Student{name='小王', age=10, studentID=1006}
Student{name='小李', age=11, studentID=1005}
Student{name='小陈', age=12, studentID=1004}

结果符合我们的预期,我们将学号大于1003的学生过滤出来了。
Java8-Lambda:初识Lambda_第1张图片
或许此时,你已经发现上面代码的问题所在。上面的两个需求除了方案一模一样之外,代码也几乎一模一样,唯一有区别的地方就是 if 判断里面的条件。假设现在再来一个需求,说:我要过滤学号小于1005的学生。那是不是我们只需要将上面的代码完全copy一份,然后只修改 if 里面的条件就可以了,就能达到我们过滤的目的。

诶,这就是我们常说的“冗余代码”,在遇到这类代码的时候,我们要做的就是优化!
那么如何优化呢?
我们常听人说,优化代码最好的方式就是利用设计模式…balabalabala
好的,下面就利用设计模式来优化代码。

优化方式一:设计模式-策略设计模式

首先我需要创建一个接口,这个接口先生说:“俺是一个过滤指定对象的接口,俺有一个方法,专门用来过滤指定对象。”

public interface StudentFilter<T> {

    boolean filter(T t);

}

俺的这个方法是你传给我一个指定对象,俺就悄悄告诉你这个对象符不符合过滤条件!” 接口先生说道。
有了接口,就需要有实现类去实现它,年龄实现类女士说:“你好,我是一个实现类,我是实现于 StudentFilter 接口先生,我的主要工作是根据年龄过滤学生。”

public class StudentAgeFilter implements StudentFilter<Student> {
    @Override
    public boolean filter(Student student) {
        return student.getAge() >= 14;
    }
}

我…我…我今天的工作是过滤出年龄大于等于14岁的学生。”,年龄实现类女士害羞的说道。

现在接口和实现类有了,就需要一个通用方法实现过滤逻辑:

    public List<Student> filterStudent(List<Student> list, StudentFilter<Student> sf){
        List<Student> students = new ArrayList<>();
        for (Student stu : list) {
            if(sf.filter(stu)){
                students.add(stu);
            }
        }
        return students;
    }

参数为:一群学生的集合和一个过滤接口。 (注意:是接口,不是实现类)
里面的实现逻辑是和上面一样的,区别在与 if 判断中,这里是调用了接口的过滤方法。

现在来测试一下过滤效果:

    @Test
    public void getStudentByAge2(){
        List<Student> list = filterStudent(students, new StudentAgeFilter());
        for (Student stu : list) {
            System.out.println(stu.toString());
        }
    }

我在调用 filterStudent 通用过滤方法的时候,第二个参数传的就是实现类,这是利用 多态 达到的效果。

结果显示:

Student{name='小王', age=10, studentID=1006}
Student{name='小李', age=11, studentID=1005}
Student{name='小陈', age=12, studentID=1004}

和我们预期的一样。

现在需求又变了,说:我要过滤学号大于1001的学生。
举一反三,我们的实现方式和上面过滤年龄是一样的。只需新增一个实现类实现于StudentFilter 接口,并且实现自己的过滤逻辑。

public class StudentIDFilter implements StudentFilter<Student> {
    @Override
    public boolean filter(Student student) {
        return student.getStudentID() > 1001;
    }
}

现在来调用测试一下:

    @Test
    public void getStudentByID2(){
        List<Student> list = filterStudent(students, new StudentIDFilter());
        for (Student stu : list) {
            System.out.println(stu.toString());
        }
    }

执行结果:

Student{name='小王', age=10, studentID=1006}
Student{name='小李', age=11, studentID=1005}
Student{name='小陈', age=12, studentID=1004}
Student{name='小明', age=13, studentID=1003}
Student{name='小红', age=14, studentID=1002}

和预期一样的结果。
Java8-Lambda:初识Lambda_第2张图片
那么注意看,使用了设计模式后,我们优化的地方的在哪里呢?
在以前的方法实现中,每一个需求我们都对应的有一个专门的实现方法,这样代码非常冗余而且耦合度高,修改起来也不方便。
而现在采用了设计模式后,我们就只需要一个 filterStudent 通用方法,不管多少个过滤需求,我始终只有这一个方法,他是不会变的,它把上面两个方法合二为一了。这是它的优势一。
那么优势二就是我们通过接口和实现类来实现业务逻辑,还给代码解了耦。以后如果有新增的需求和修改的需求,我只需要新增或修改实现类就可以了,根本不用动 filterStudent 这个方法。
但是这个优势二也是它的一个缺陷。
它的缺陷就是,每当我在新增需求的时候,我都需要新增一个对应的实现类,麻不麻烦啊。有没有更简单的办法呢?答案是有的,有请
Java8-Lambda:初识Lambda_第3张图片

优化方式二:匿名内部类

在使用了匿名内部类后,就可以不用去新增实现类了,代码如下:

    @Test
    public void filterStudentInnerClass(){
        List<Student> list =  filterStudent(students, new StudentFilter<Student>() {
            @Override
            public boolean filter(Student student) {
                return student.getStudentID() <= 1002;
            }
        });
        for (Student stu : list) {
            System.out.println(stu.toString());
        }
    }

直接在 Test 方法中调用我们的 filterStudent 方法,方法的第二个参数我们就是用匿名内部类来实现。

这样做的话就简便太多了。

在文章开篇就讲了,匿名内部类看起来有点杂乱(再吐槽一次…),我们可以使用 Lambda 表达式来优化它。优化如下

优化方式三:Lambda 表达式

    @Test
    public void filterStudentLambda(){
        List<Student> list = filterStudent(students, (x)->x.getStudentID() >= 1004);
        list.forEach(System.out::println);
    }

优化后的代码十分简洁。

回头看,我们先从每一个需求都有一个自己专门的实现方法,代码冗余且耦合度高,再到使用设计模式来进行优化并解耦,但是根据需求而新增的实现类是这设计模式的痛点,再到使用匿名内部类来代替实现类,最后到使用 Lambda 表达式简化代码。

我们每一步都在逐步的减少代码量。

在上面我们使用 Lambda 表达式的时候依赖了 filterStudent 方法和 接口StudentFilter,其实Java8中还有一个方法可以做到不依赖这两个东西。

优化方式四: Stream API

    @Test
    public void streamTest(){
        students.stream()
                .filter((e)->e.getName().equals("小红"))
                .forEach(System.out::println);
    }

打印结果:

Student{name='小红', age=14, studentID=1002}

这个 Stream 只是一个引子,它会在 Lambda 学习后紧跟着学习。很是方便。
但是这里也要提个醒,不要在代码中大量的使用 Lambda和Stream。因为对于不了解他们的同事来说,代码阅读起来时候很困难的。

好啦,我走了…
Java8-Lambda:初识Lambda_第4张图片


技 术 无 他, 唯 有 熟 尔。
知 其 然, 也 知 其 所 以 然。
踏 实 一 些, 不 要 着 急, 你 想 要 的 岁 月 都 会 给 你。


你可能感兴趣的:(Java)