一、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表达式直接完成输出,用起来更加方便。