Java8 新特性 Lambda表达式的基本用法

一、Lambda表达式

Lambda表达式是一个匿名函数 ,我们可以把Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。有了Lambda表达式使得Java的函数式编程更加方便,代码更加简洁。

二、Lambda表达式的例子

一个简单的例子

分别写两个测试用例实现相同的功能,一个使用匿名内部类,一个使用Lambda表达式,体会二者的差异。
需求:新建一个list数组,放入一些数,对数组进行排序并打印输出。

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.util.*;
import java.util.function.Consumer;

public class TestLambda {
    
    List list;
    
    @Before
    public void setup(){
        list = new ArrayList<>();
        list.add(3);
        list.add(5);
        list.add(99);
        list.add(1);
    }
    
    @After
    public void tearDown(){
        list = null;
    }

    //匿名内部类
    @Test
    public void test1(){

        list.sort( new Comparator() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return Integer.compare(o1, o2);
            }
        });
        list.forEach(new Consumer() {
            @Override
            public void accept(Integer integer) {
                System.out.println(integer);
            }
        });
    }

    //Lambda表达式
    @Test
    public void test2(){
        list.sort((o1, o2) -> Integer.compare(o1, o2));
        list.forEach(integer -> System.out.println(integer));
    }
}

可以看到在使用Lambda表达式能够明显减少代码量,并且具有较好的可读性。

一个更加复杂的例子

假设我们有一个Employee类如下:

class Employee{
    private String name;
    private int age;
    private int salary;

    public Employee() {
    }

    public Employee(String name, int age, int salary) {
        this.name = name;
        this.age = age;
        this.salary = salary;
    }

    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 int getSalary() {
        return salary;
    }

    public void setSalary(int salary) {
        this.salary = salary;
    }

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

现在需要对这个类进行一些统计操作:
1、直接声明函数实现统计功能。(无优化)
需要对所有员工中年龄大于40的筛选出来,我们可能会写一个筛选函数。

    //一个更加复杂的例子
    @Test
    public void test3() {
        List employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );
        //需求:获取年龄大于40的员工;
        List result1 = filterEmployees(employees);
        for (Employee e : result1) {
            System.out.println(e);
        }
    }

    //获取年龄大于40的员工;
    public List filterEmployees(List list) {
        List employees = new ArrayList<>();
        for (Employee employee : list) {
            if (employee.getAge() >= 40) {
                employees.add(employee);
            }
        }
        return employees;
    }

如果这样的需求很多,例如要求筛选员工工资大于10000的,我们又需要添加一个筛选函数。

    //需求:获取当前公司中员工工资大于10000的员工信息
    public List filterEmployees2(List list) {
        List employees = new ArrayList<>();
        for (Employee employee : list) {
            if (employee.getSalary() >= 10000) {
                employees.add(employee);
            }
        }
        return employees;
    }

可见,每增加一个需求,就会新增一个筛选函数。仔细观察会发现,上面两个筛选函数中只有if判断语句中不一样,因此可以将筛选函数提取到接口中,使代码更加清晰。

2、提取函数公共部分,即使用策略设计模式(针对接口编程而不是实现编程)。
因为筛选函数中存在大量冗余代码,将这些冗余代码提出,并用接口方式实现。
定义泛型接口如下,当中只有test一个泛型方法:

interface MyPredicate{
    boolean test(T t);
}

当我们需要增加一个筛选功能时,只需要新建一个类实现该接口,即实现test函数来添加筛选条件,例如筛选40岁以上员工的代码如下:

class FilterEmployeeByAge implements MyPredicate {
    @Override
    public boolean test(Employee employee) {
        return employee.getAge()>=40;
    }
}

于是,我们可以针对所有的筛选定义一个统一的函数,任何的筛选条件都通过调用该方法进行筛选。

    public List fileterEmployee(List list, MyPredicate myPredicate) {
        List employees = new ArrayList<>();
        for (Employee employee : list) {
            if (myPredicate.test(employee)) {
                employees.add(employee);
            }
        }
        return employees;
    }

具体使用方法如下:

    @Test
    public void test4(){
        List employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );

        List employeeList = fileterEmployee(employees, new FilterEmployeeByAge());
        for (Employee employee : employeeList) {
            System.out.println(employee);
        }
    }

这样一来,所有的筛选都只需要调用filterEmployee这一个方法,只需要传入过滤接口的不同实现类即可。

3、匿名内部类。可以看到,使用策略设计模式,虽然使得函数的公共部分被提取出来,但针对每一种不同的筛选条件,仍然需要新建一个实现了特定接口的类。如果这个类仅使用一次,没有被复用的机会,显然类的定义就显得多余。于是,可以使用匿名内部类进行替换,我们不需要定义类来实现接口而直接使用new关键字新建一个满足该接口(实现了该接口的方法)的对象。
例如使用匿名内部类的方法筛选工资小于10000的员工。

    @Test
    public void test5(){
        List employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );

        List employeeList = fileterEmployee(employees, new MyPredicate() {
            @Override
            public boolean test(Employee employee) {
                return employee.getSalary()<10000;
            }
        });
        for (Employee employee : employeeList) {
            System.out.println(employee);
        }
    }

4、Lambda表达式。如果需要实现的接口仅有一个抽象方法,那么我们实现该接口时目的是明确的,就是为了实现该接口中定义的仅有的一个抽象方法的功能。所以匿名内部类也可以省去,直接使用Lambda表达式传递“代码”,告诉这个仅有的抽象方法应该如何实现。上方的匿名内部类也可以替换为如下Lambda表达式。函数式接口的声明可以在接口上方加上@FunctionalInterface注解

    @Test
    public void test6(){
        List employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );

        List employeeList = fileterEmployee(employees, employee -> employee.getSalary()<10000);
        for (Employee employee : employeeList) {
            System.out.println(employee);
        }
    }

5、Stream API。在Lambda表达式进行几乎极简优化的情况下,Java8中还有一项新特性能够更好的对集合进行处理,即Stream API。通过调用Stream API提供的filter方法,甚至不需要定义上述的过滤方法和接口,直接结合Lambda表达式完成数据的筛选操作。

    @Test
    public void test7() {
        List employees = Arrays.asList(
                new Employee("张三", 20, 12345),
                new Employee("李四", 50, 23142),
                new Employee("王二", 42, 2309),
                new Employee("麻子", 35, 1233)
        );

//        List employeeList = employees.stream().filter((e)->e.getSalary()<10000).collect(Collectors.toList());
//        for (Employee employee : employeeList) {
//            System.out.println(employee);
//        }
        employees.stream().filter((e)->e.getSalary()<10000).forEach(employee -> System.out.println(employee));
    }

上方注释可以进行筛选并存储到集合,或者直接使用forEach结合lambda表达式直接完成输出,用起来更加方便。

你可能感兴趣的:(Java8 新特性 Lambda表达式的基本用法)