文章目录
一、简介
Lambda是一个匿名函数,我们可以把Lambda理解为一段可以传递的代码(将代码像数据一样传递)。
二、 回顾匿名内部类
假设我们有一个员工类Employee,我们要用一些条件对公司的员工进行筛选,为了提高代码的复用性,我们可以设计一个专门的接口:
interface MyPredicate<T> {
public boolean condition(T t);
}
在接口中的方法condition()
就是我们要进行筛选的条件,针对不同的筛选条件,我们只需要多次实现这个接口即可:
class FilterEmployByAge implements MyPredicate<Employee> {
@Override
public boolean condition(Employee employee) {
return employee.getAge() > 35;
}
}
在业务处理过程中,我们调用这个接口,其实真正需要的是它包含的condition()方法。换句话说filterEmployees1()
希望接受一段方法代码作为参数,但没有办法直接传递这个方法代码本身,只能传递一个接口:
public List<Employee> filterEmployees1(List<Employee> list, MyPredicate<Employee> myPredicate) {
List<Employee> result = new ArrayList<>();
for (Employee employee : list) {
if (myPredicate.condition(employee)) {
result.add(employee);
}
}
return result;
}
针对这种情况,我们可以使用匿名内部类的方式来简化代码:
在选择筛选条件的时候,使用匿名内部类的方式实现MyPredicate接口:
//无需再专门实现MyPredicate接口,但是filterEmployees1()方法还是需要写
List<Employee> result2 = test.filterEmployees1(employees, new MyPredicate<Employee>() {
@Override
public boolean condition(Employee employee) {
return employee.getAge() > 35;
}
});
三、初识Lambda
import sun.security.mscapi.CPublicKey;
import java.util.*;
public class Lambda {
public static void main(String[] args) {
// 匿名内部类写法
Comparator<Integer> comparator = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o1, o2);
}
};
// 使用匿名内部类
TreeSet<Integer> treeSet = new TreeSet<>(comparator);
// 匿名内部类-->Lambda表达式
Comparator<Integer> comparator1 = (x, y) -> Integer.compare(x, y);
// 使用Lambda表达式
TreeSet<Integer> treeSet1 = new TreeSet<>(comparator1);
List<Employee> employees = Arrays.asList(
new Employee("A", 12, 12.11),
new Employee("B", 18, 99.00),
new Employee("C", 46, 45.11),
new Employee("D", 50, 34.32),
new Employee("E", 47, 78.12),
new Employee("F", 67, 34.56),
new Employee("G", 21, 100)
);
// 需求:获取所有员工年龄大于35岁的员工信息
Lambda test = new Lambda();
// 普通实现方式
List<Employee> result = test.filterEmployees(employees);
for (Employee employee : result) {
System.out.println(employee);
}
System.out.println("____________________________________");
// 优化1:策略模式,在调用的时候选择策略(接口的实现),方便复用
List<Employee> result1 = test.filterEmployees1(employees, new FilterEmployByAge());
for (Employee employee : result1) {
System.out.println(employee);
}
// 优化2:策略模式+匿名内部类(比单纯的策略模式省去了一个额外的调用策略的方法)
List<Employee> result2 = test.filterEmployees1(employees, new MyPredicate<Employee>() {
@Override
public boolean condition(Employee employee) {
return employee.getAge() > 35;
}
});
System.out.println("____________________________________");
for (Employee employee : result2) {
System.out.println(employee);
}
// 优化3:策略模式+Lambda(比匿名内部类又少了很多冗余代码)
List<Employee> result3 = test.filterEmployees1(employees, (e) -> e.getAge() > 35);
System.out.println("____________________________________");
result3.forEach(System.out::println);
// 优化4:stream
System.out.println("____________________________________");
employees.stream()
.filter((e) -> e.getAge() > 35)
.forEach(System.out::println);
}
// 普通实现方式
public List<Employee> filterEmployees(List<Employee> list) {
List<Employee> result = new ArrayList<>();
for (Employee employee : list) {
if (employee.getAge() > 35) {
result.add(employee);
}
}
return result;
}
// 调用策略模式的方法
public List<Employee> filterEmployees1(List<Employee> list, MyPredicate<Employee> myPredicate) {
List<Employee> result = new ArrayList<>();
for (Employee employee : list) {
if (myPredicate.condition(employee)) {
result.add(employee);
}
}
return result;
}
}
//策略模式接口
interface MyPredicate<T> {
public boolean condition(T t);
}
//策略模式的策略,每多一个策略,需要额外建一个类,不方便
class FilterEmployByAge implements MyPredicate<Employee> {
@Override
public boolean condition(Employee employee) {
return employee.getAge() > 35;
}
}
//实体类
class Employee {
private String name;
private int age;
private double salary;
public Employee() {
}
@Override
public String toString() {
return "Employee{" +
"name='" + name + '\'' +
", age=" + age +
", salary=" + salary +
'}';
}
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;
}
}
四、Lambda基础语法
1.箭头操作符
Java8新引入了一个操作符“->”,我们称为箭头操作符,或Lambda操作符。
箭头操作符将Lambda表达式拆分为两部分:
左侧:Lambda 表达式的参数列表。(对应接口中抽象方法的参数列表)
右侧:Lambda 表达式中所需要执行的功能,即Lambda 体。(对象接口中抽象方法的实现)
2.语法格式
格式1:对应的抽象方法无参数列表,无返回值。
示例:
//实现
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};
//使用Lambda实现
Runnable r1 = () -> System.out.println("hello");
//两种调用
r.run();
r1.run();
格式2:对应抽象方法有一个参数,无返回值。
note:
- 如果只有一个参数,那么参数的小括号可以省略。
- Lambda表达式的参数列表数据类型可以省略不写,因为JVM编译器可以通过上下文推断出参数的数据类型(类型推断)。
- Lambda表达式可以赋值给函数式接口,相当于实现一个简略版匿名内部类(不需要new)。
Consumer<String> con = (x) -> System.out.println(x);
con.accept("hello");
System.out.println(con);
格式3:对应抽象方法有多个参数、多条语句,以及返回值。
note: 如果Lambda函数中有多条语句,那么需要大括号。
Comparator<Integer> comparator=(x,y)->{
int temp=Integer.compare(x,y);
return temp;
};
格式4:对应抽象方法有多个参数、一条语句,以及返回值。
note: 只有一条语句,那么大括号和return 都可以省略。
Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y);
3.函数式接口
接口中只有一个抽象方法,这个接口称为函数式接口。一般会在接口前添加注解@FunctionalInterface
来检查是否满足函数式接口,使用示例可见demo。
note:
- Lambda表达式需要函数式接口的支持。
- 选择函数式接口根据每个接口的特点来选择,比如Function与Comsumer的区别在于,前者的抽象函数有返回值,返回类型可以与入参不一致,可用于操作参数后生成新的对象;后者没有返回值,可用于改变入参对象。
一些常见的函数式接口:
4.注意项
与匿名内部类类似,Lambda表达式也可以访问定义在主体代码外部的变量,但对于局部变量,它也只能访问final类型的变量,与匿名内部类的区别是,它不要求变量声明为final,但变量事实上不能被重新赋值。
eg.
String test="hello";
//此时如果对test变量进行修改,编译器会报错
Runnable r1 = () -> System.out.println(test);
r1.run();
Java会将test的值作为参数传递给Lambda表达式,为Lambda表达式建立一个副本,它的代码访问的是这个副本,而不是外部声明的msg变量。如果允许test被修改,则程序员可能会误以为Lambda表达式读到修改后的值,引起更多的混淆。
进一步深究,为什么要建立副本,而不是直接访问外部变量,这是因为test变量定义在栈中,Lambda表达式被执行的时候,这个变量可能已经被释放。如果希望传入的值可以修改,可以考虑将变量定义为实例变量或者数组,这种值被修改后,内存地址不会变的对象。
五、方法引用与构造器引用
4.1 基本概念
方法引用:若Lambda体中的内容有方法已经实现,我们可以使用方法引用。(可以理解为方法引用是Lambda表达式的另一种表现形式)
构造器引用:与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法。
4.2 使用前提
使用方法引用,那么这个方法的参数与返回类型,要与函数式接口中抽象方法的保持一致。
例如上面提到的forEach(System.out::println)
这里println的内容和传入forEach的内容一致。
使用构造器引用,构造器参数列表要与接口中抽象方法的参数列表一致(想要调用不同的构造函数,只要更改接口中抽象方法的参数即可)。
4.3 语法格式
方法引用的基本格式:
格式1:对象::实例方法名
格式2:类::静态方法名
格式3:类::实例方法名
构造器引用基本格式:
ClassName::new
六、一些Demo
1.调用Collections.sort()方法,通过定制排序比较Employee对象(先比较年龄,年龄相同比较名字):
// 自定义排序规则
Collections.sort(employees, (o1, o2) -> {
if (o1.getAge() == o2.getAge()) {
return o1.getName().compareTo(o2.getName());
} else {
return Integer.compare(o1.getAge(), o2.getAge());
}
} );
2.利用策略模式,声明函数式接口,接口中声明抽象方法getValue,在另一个类中编写一个方法使用接口作为参数,将一个字符串变为大写:
public class LambdaTest {
public static void main(String args[]) {
// 在用的时候再指定策略,这里也可以使用匿名内部类的方式来实现
System.out.println(toUpper("aabbcc",x->x.toUpperCase()));
}
// 调用策略的方法
public static String toUpper(String str,Operator operator){
return operator.getValue(str);
}
}
//策略接口
interface Operator{
public String getValue(String str);
}
3.函数型接口Function<T,R> Demo
对类型为T的入参处理后,返回值的类型为R。
public class LambdaTest {
public static void main(String args[]) {
System.out.println(strHandle("AABBCC",x->x.toLowerCase()));
}
public static String strHandle(String str, Function<String,String> function){
return function.apply(str);
}
}