1.简介
Lambda
是一个
匿名函数
,我们可以把
Lambda 表达式理解为是
一段可以传递的代码
(将代码像数据一样进行传递)。
2.为什么使用Lambda表达式
- 避免匿名内部类定义过多
- 可以让代码看起来很简洁
- 去掉了一堆没有意义的代码,只留下核心的逻辑
3.函数式接口
定义:任何接口,如果只
包含一个抽象方法,那么他就是一个函数式接口。
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
理解Functional Interface(函数式接口)是学习 Lambda 表达式的关键所在,我们可以通过 Lambda 表达式来创建该接口的对象。
Lambda 表达式需要“函数式接口”的支持。
我们可以使用注解
@FunctionalInterface修饰接口,注解@FunctionalInterface能检查接口是否是函数式接口。
函数式接口特点:
- 接口有且仅有一个抽象方法
- 允许定义静态方法
- 允许定义默认方法
- 允许java.lang.Object中的public方法
- @FunctionalInterface注解不是必须的,如果一个接口符合“函数式接口”定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是“函数式接口”,但是加上了@FunctionalInterface注解,那么编译器会报错
4.Lambda表达式语法
(int a, int b) -> {return a+b;};
本质是一个函数,函数有返回值、方法名、参数列表、方法体。
int add(int a, int b) {
return a + b;
}
Lambda表达式函数只有参数列表和方法体
(参数列表) -> {方法体}
说明:
- ():用来描述参数列表;
- {}:用来描述方法体;
- ->:Lambda 操作符或叫剪头操作符
- Lambda只需要一个参数时,参数的小括号可以省略
-
当 Lambda 体只有一条语句时,return 与大括号可以省略
-
Lambda 表达式参数列表的数据类型可以省略不写,因为JVM编译器能通过上下文推断出数据类型,即“类型推断”。
5.推导Lambda表达式
例子:
package com.mcs.lambda;
public class Employee {
private String name;
private int age;
private double salary;
public Employee() {
}
public Employee(String name) {
this.name = name;
}
public Employee(String name, int age, double 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 double getSalary() {
return salary;
}
public void setSalary(double salary) {
this.salary = salary;
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
}
package com.mcs.lambda;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TestLambda {
List<Employee> emps = Arrays.asList(
new Employee("张三", 18, 9999.99),
new Employee("李四", 59, 6666.66),
new Employee("王五", 28, 3333.33),
new Employee("赵六", 17, 7777.77),
new Employee("田七", 38, 5555.55)
);
// 需求:获取当前公司中年龄大于 35 的员工信息
public List<Employee> filterEmployeeAge(List<Employee> emps) {
List<Employee> list = new ArrayList<>();
for (Employee emp : emps) {
if (emp.getAge() >= 35) {
list.add(emp);
}
}
return list;
}
@Test
public void test1() {
System.out.println("----原始方法,获取年龄大于35的员工信息----");
List<Employee> list = filterEmployeeAge(emps);
for (Employee employee : list) {
System.out.println(employee);
}
System.out.println("------------------------------------");
}
//新需求:获取当前公司中工资大于 5000 的员工信息
public List<Employee> filterEmployeeSalary(List<Employee> emps) {
List<Employee> list = new ArrayList<>();
for (Employee emp : emps) {
if (emp.getSalary() >= 5000) {
list.add(emp);
}
}
return list;
}
@Test
public void test2() {
System.out.println("----原始方法,获取工资大于5000的员工信息----");
List<Employee> list = filterEmployeeSalary(emps);
for (Employee employee : list) {
System.out.println(employee);
}
System.out.println("--------------------------------------");
}
// 通过以上方法,每新增一个需求就需要新写一个方法。存在大量重复代码,冗余
// 优化方式一:使用策略设计模式,定义MyPredicate接口和实现类
public List<Employee> filterEmployee(List<Employee> emps, MyPredicate<Employee> mp) {
List<Employee> list = new ArrayList<>();
for (Employee employee : emps) {
if (mp.test(employee)) {
list.add(employee);
}
}
return list;
}
@Test
public void test3() {
System.out.println("----策略设计模式,获取年龄大于35的员工信息----");
List<Employee> list = filterEmployee(emps, new FilterEmployeeByAge());
for (Employee employee : list) {
System.out.println(employee);
}
System.out.println("---------------------------------------");
System.out.println("----策略设计模式,获取工资大于5000的员工信息----");
List<Employee> list2 = filterEmployee(emps, new FilterEmployeeBySalary());
for (Employee employee : list2) {
System.out.println(employee);
}
System.out.println("---------------------------------------");
}
// 通过策略模式发现,每次来新需求,还得新建一个实现类
// 优化方式二:匿名内部类
@Test
public void test4() {
System.out.println("----匿名内部类,获取年龄大于35的员工信息----");
List<Employee> list = filterEmployee(emps, new MyPredicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getAge() >= 35;
}
});
for (Employee employee : list) {
System.out.println(employee);
}
System.out.println("--------------------------------------");
}
// 通过匿名内部类发现,匿名内部类中有用的代码只有一行,但是写了好几行代码,可读性差
// 优化方式三:Lambda 表达式
@Test
public void test5(){
System.out.println("----lambda表达式,获取年龄大于35的员工信息----");
List<Employee> list = filterEmployee(emps, (e) -> e.getAge() >= 35);
list.forEach(System.out::println);
System.out.println("-----------------------------------------");
}
//优化方式四:Stream API
@Test
public void test6(){
System.out.println("----Stream API,获取年龄大于35的员工信息----");
emps.stream().filter(e -> e.getAge() >= 35).forEach(System.out::println);
System.out.println("----------------------------------------");
}
}
package com.mcs.lambda;
@FunctionalInterface
public interface MyPredicate<T> {
public boolean test(T t);
}
package com.mcs.lambda;
public class FilterEmployeeByAge implements MyPredicate<Employee>{
@Override
public boolean test(Employee t) {
return t.getAge() >= 35;
}
}
package com.mcs.lambda;
public class FilterEmployeeBySalary implements MyPredicate<Employee> {
@Override
public boolean test(Employee t) {
return t.getSalary() >= 5000;
}
}
6.Java8 内置四大核心函数式接口
使用Lambda表达式必须先有函数式接口,为了不让我们自己手动创建接口,Java8内置了一些接口供我们使用。最常用的四个接口如下:
函数式接口 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T
> 消费型接
口
| T | void |
对类型为T的对象应用操作,
包含方法: void accept(T t)
|
Supplier<T
> 供给型接
口
| 无 | T |
返回类型为T的对象,
包含方法:T get();
|
Function<T, R>
函数型接口
| T | R |
对类型为T的对象应用操作,并返回结果是R类型的对象。
包含方法:R apply(T t);
|
Predicate<T
> 断定型接
口
| T | boolean |
确定类型为T的对象是否满足某约束,并返回boolean 值。
包含方法boolean test(T t);
|
Java还有很多内置函数式接口,以上四个是核心
7.方法引用和构造器引用
方法引用:若Lambda体中的内容有方法已经实现了,我们可以使用“方法引用”。(可以理解为方法引用是Lambda表达式的另一种表现形式)
方法的返回值类型和参数列表必须与函数式接口的抽象方法的返回值类型和参数列表一致
方法引用:使用操作符 “
::
” 将方法名和对象或类的名字分隔开来。主要有三种语法形式:
- 对象::实例方法
-
类::静态方法
- 类::实例方法(如果第一个参数是实例方法的调用者,第二个参数是实例方法的参数时,可以使用类::实例方法)
package com.mcs.lambda;
import org.junit.Test;
import java.io.PrintStream;
import java.util.function.Consumer;
public class TestMethodRef {
// 对象::实例方法
@Test
public void test1() {
PrintStream out = System.out;
Consumer<String> con = x -> out.println(x);
con.accept("test1");
// 等同于
PrintStream out1 = System.out;
Consumer<String> con1 = out1::println;
con1.accept("test1");
}
}
构造器引用:
格式:ClassName::new
构造器的参数列表,需要与函数式接口中参数列表保持一致!
package com.mcs.lambda;
import org.junit.Test;
import java.util.function.Function;
import java.util.function.Supplier;
public class ConstructorRef {
@Test
public void test1() {
Supplier<Employee> sup = () -> new Employee();
// 等同于
Supplier<Employee> sup1 = Employee::new;
Employee employee = sup1.get();
System.out.println(employee);
Function<String, Employee> fun = (x) -> new Employee(x);
Function<String, Employee> fun1 = Employee::new;
Employee employee1 = fun1.apply("张三");
System.out.println(employee1);
}
}
数组引用:
格式:type[]::new
package com.mcs.lambda;
import org.junit.Test;
import java.util.function.Function;
public class ArrayRef {
@Test
public void test1() {
Function<Integer, String[]> fun1 = (x) -> new String[x];
String[] s1 = fun1.apply(10);
System.out.println(s1.length);
// 等同于
Function<Integer, String[]> fun2 = String[]::new;
String[] s2 = fun2.apply(10);
System.out.println(s2.length);
}
}