本文正在参加「Java主题月 – Java Debug笔记活动」,详情查看 活动链接
为什么使用Lamdba表达式
我来举一个范例就知道了
使用匿名内部类
public void test1(){
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return Integer.compare(o2,o1);
}
};
}
复制代码
上面那么多的代码其实就一句话有用Integer.compare(o2,o1);
使用Lambda表达式
public void test2(){
Comparator<Integer> com = (o1, o2) -> Integer.compare(o1, o2);
}
复制代码
是不是很方便了。但可能也会有人觉得并没有什么太大的简便。那么下面再给出一个例子来引入 Lambda 表达式。
创建 Employee 类,有name、age、salary三个属性
public class Employee {
private String name;
private int age;
private double salary;
public Employee() {
}
public Employee(String name, int age, double salary) {
this.name = name;
this.age = age;
this.salary = salary;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public double getSalary() {
return salary;
}
@Override
public String toString() {
return "name='" + name + '\'' +
", age=" + age +
", salary=" + salary;
}
}
复制代码
- 需求1:获取员工年龄大于35的员工信息
我们模拟一下数据库,所以准备一个 List 集合
//Arrays.asList 将数组转换成集合
List<Employee> employees = Arrays.asList(
new Employee("张三",18,9999.99),
new Employee("李四",38,5555.55),
new Employee("王五",50,6666.66),
new Employee("赵六",16,3333.33),
new Employee("田七",8,7777.77)
);
复制代码
既然来了需求那我们就要开发呀
/**
* 获取员工年龄大于35的员工信息
* @param list
* @return
*/
public List<Employee> findEmployeesByAge(List<Employee>list){
List<Employee>emps = new ArrayList<>();
for(Employee emp : list){
if(emp.getAge() > 35){
emps.add(emp);
}
}
return emps;
}
复制代码
并且来波开发自测
@Test
public void test3(){
List<Employee> employeesByAge = findEmployeesByAge(employees);
for (Employee employee : employeesByAge) {
System.out.println(employee);
}
}
输出结果
name='李四', age=38, salary=5555.55
name='王五', age=50, salary=6666.66
复制代码
完美!!!美滋滋。完成任务。然后又来了一个需求
- 需求2:获取公司中工资大于5000的员工信息
public List<Employee> findEmployeesBySalary(List<Employee>list){
List<Employee>emps = new ArrayList<>();
for(Employee emp : list){
if(emp.getSalary() > 5000){
emps.add(emp);
}
}
return emps;
}
复制代码
你写完之后发现,特么…就改了个方法名和判断条件,聪明的你发现这代码不能这么写。为什么不能这么写呢!如果你牛逼的客户**,突然又来了一个需求,获取年龄大于20的,薪资大于3000的。这要怎么办
优化一:设计模式之策略模式
创建查询行为的接口
public interface MyPredicate<T> {
public boolean test();
}
复制代码
并创建相关的实现类代表不同的行为: (分别是年龄> 35和工资> 5000的 )
//获取员工年龄大于35的员工信息
public class FindEmployeesByAge implements MyPredicate<Employee>{
@Override
public boolean test(Employee emp) {
return emp.getAge() > 35;
}
}
//获取公司中工资大于5000的员工信息
public class FindEmployeesBySalary implements MyPredicate<Employee>{
@Override
public boolean test(Employee emp) {
return emp.getSalary() > 5000;
}
}
复制代码
在Test
类中,给外部提供一个通用方法
public List<Employee> filterEmployees(List<Employee>list,MyPredicate<Employee>mp){
List<Employee>emps = new ArrayList<>();
for(Employee emp : list){
if(mp.test(emp)){ //重点代码
emps.add(emp);
}
}
return emps;
}
复制代码
写了这么久自测一下
@Test
public void test4(){
// 传入 FindEmployeesByAge 获取员工年龄大于35的员工信息
List<Employee> employees = filterEmployees(this.employees, new FindEmployeesByAge());
for (Employee employee : employees) {
System.out.println(employee);
}
}
输出结果
name='李四', age=38, salary=5555.55
name='王五', age=50, salary=6666.66
复制代码
如果想要测试工资大于5000的员工信息,只需要换掉 filterEmployees(this.employees, new FindEmployeesByAge())
中的FindEmployeesByAge为FindEmployeesBySalary
又觉得上面那种方式,来一个需求就需要创建一个实现类,那还有没有其他方式呢?
优化二:匿名内部类
好处:不需要创建接口的具体的实现类
(但还是需要MyPredicate
接口和filterEmployees()
方法):
@Test
public void test5(){
List<Employee> employees = filterEmployees(this.employees, new MyPredicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getAge() >= 18;
}
});
for (Employee employee : employees) {
System.out.println(employee);
}
}
输出结果
name='张三', age=18, salary=9999.99
name='李四', age=38, salary=5555.55
name='王五', age=50, salary=6666.66
复制代码
优化三:Lambda表达式
哈哈哈,优化优化就回到了开头那个例子(*^▽^*)
,Lambda 表达式省去 匿名内部类 的没用的代码,提高了代码的整洁性和可读性(注还是需要那个filterEmployees方法
)
@Test
public void test6(){
List<Employee> employees = filterEmployees(this.employees, (e) -> e.getAge() > 15);
for (Employee employee : employees) {
System.out.println(employee);
}
}
输出结果
name='张三', age=18, salary=9999.99
name='李四', age=38, salary=5555.55
name='王五', age=50, salary=6666.66
name='赵六', age=16, salary=3333.33
复制代码
将这一段代码
new MyPredicate<Employee>() {
@Override
public boolean test(Employee employee) {
return employee.getAge() >= 18;
}
});
简化为了:(e) -> e.getAge() > 15
复制代码
优化四:使用 Stream API
使用该方式,不需要我们上面所创建的所有方法,包括接口、实现类。当然数据还是要的。(这里先提一句)
@Test
public void test7(){
employees.stream()
.filter((e) -> e.getAge() > 35)
.forEach(System.out::println);
}
输出结果
name='李四', age=38, salary=5555.55
name='王五', age=50, salary=6666.66
复制代码
Lambda表达式基础语法
Java8中引入了一个新的操作符,”->”,该操作符称为箭头操作符
或者Lambda操作符
,箭头操作符将 Lambda 表达式拆分成两部分:
- 左侧: Lambda 表达式的参数列表,也就是接口中抽象方法的参数列表
- 右侧: Lambda 表达式中所需要执行的功能(即:Lambda体),对应的是对抽象方法的实现
注:Lambda表达式需要函数式接口的支持。所谓函数式接口就是接口中只能有一个抽象方法
基础语法
- 语法格式一:无参数,无返回值
@Test
public void test1(){
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("普通 Runnable");
}
};
runnable.run();
System.out.println("=============我是分割线=============");
Runnable runnable1 = () -> System.out.println("Lambda Runnable");System.out.println("Lambda Runnable");
runnable1.run();
}
输出
普通 Runnable
=============我是分割线=============
Lambda Runnable
复制代码
- 语法格式二:一个参数、无返回值(
参数列表中有且只有一个参数,那么小括号可以省略不写
)
@Test
public void test2(){
//Consumer 后面会介绍到
//Consumer con = (x) -> System.out.println(x);
Consumer con = x -> System.out.println(x);
con.accept("NiuBi Class");
}
输出
NiuBi Class
复制代码
- 语法格式三: 多个参数,有返回值,Lambda 体只有一条语句(
大括号和return可以省略
)
@Test
public void test3(){
Comparator<Integer> com = (x,y) -> Integer.compare(x, y);
int compare = com.compare(12, 333);
System.out.println(compare);
}
输出
-1
复制代码
- 语法格式四:多个参数,有返回值,Lambda体有多条语句(
要用大括号括起来,并且要写上return
)
@Test
public void test4(){
Comparator<Integer>com = (x,y) -> {
System.out.println("函数式接口");
return Integer.compare(x,y);
};
// 执行com.compare这代码之后 Lambda体才被执行
int compare = com.compare(1211, 333);
System.out.println(compare);
}
输出
函数式接口
1
复制代码
- 语法格式五:Lambda 表达式的参数列表的数据类型可以省略不写,因为 JVM 编译器可以通过上下文推断出数据类型,这过程称之为”类型推断”,例如:
(Integer x,Integer y ) -> Integer.compare(x,y); 可以简写成(x,y) -> Integer.compare(x,y);
函数式接口
-
接口中只有一个抽象方法的接口称为函数式接口
-
使用注解
@FunctionlInterface
来检查是否是函数式接口
接下来,我们来创建一个函数式接口, 对一个数进行+ - * /
的运算
@FunctionalInterface
public interface MyFun {
Integer getValue(Integer num);
}
复制代码
在Test
类增加通用方法:
public Integer operation(Integer num,MyFun mf){
return mf.getValue(num);
}
复制代码
测试:
@Test
public void test5(){
System.out.println(this.operation(100,(x) -> x * x ));
}
复制代码
再提一嘴:
“->”,该操作符称为箭头操作符
或者Lambda操作符
,箭头操作符将 Lambda 表达式拆分成两部分:
-
左侧: Lambda 表达式的参数列表,也就是接口中抽象方法的参数列表。
Integer getValue(Integer num);
,也就是num
。 -
右侧: Lambda 表达式中所需要执行的功能(即:Lambda体),对应的是对抽象方法的实现。相当于
@Override
public Integer getValue(Integer num) {
return num * num;
}
复制代码
四大内置函数式接口
我们发现,如果使用 Lambda 还要自己写一个函数式接口的话太麻烦,所以 Java8 提供四大内置函数式接口,只有一小部分比较特殊的情况需要我们自己去定义函数式接口
Consumer<T>
消费型接口void accept(T t);
:对类型为T的对象应用操作Supplier<T>
供给型接口T get();
:返回类型为T的对象Function<T, R>
函数型接口R apply (T t);
:对类型为T的对象应用操作,并返回结果,结果是R类型的对象Predicate<T>
断言形接口boolean test(T t);
:确定类型为T的对象是否满足某约束,并返回boolean值,满足为 true
Consumer<T>
消费型接口 void accept(T t);
@Test
public void test1() {
//例子1
happy(1000,(x)->System.out.println("你在我这消费了" + x + "元"));
//例子2
List<String> list = Arrays.asList("1", "2", "3");
list.forEach((obj) -> System.out.println(obj));
}
public void happy(Integer i, Consumer<Integer> con){
// 执行 Lamdba 体
con.accept(i);
}
复制代码
Supplier<T>
供给型接口 T get();
需求: 产生指定个数的整数,并放入集合中
@Test
public void test2(){
List<Integer> numList = getNumList(5, () -> (int) (Math.random() * 100));
numList.forEach((obj)->System.out.println(obj));
}
public List<Integer> getNumList(int num, Supplier<Integer> sup){
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
// 执行 Lamdba 体
Integer value = sup.get();
list.add(value);
}
return list;
}
复制代码
Function<T, R>
函数型接口 R apply (T t);
@Test
public void test3(){
//去掉空格
System.out.println(strHandler(" gongj ",(str) -> str.trim()));
//转换大小写
System.out.println(strHandler("gongj", (str) -> str.toUpperCase()));
}
private String strHandler(String str, Function<String,String> fun){
// 执行 Lamdba 体
return fun.apply(str);
}
复制代码
Predicate 断言形接口 boolean test(T t);
@Test
public void test4(){
List<String> list = Arrays.asList("gongj", "yuanj", "xdd");
System.out.println( filterStr(list,(str) -> str.length() > 3));
}
//判断长度>3的字符串
public List<String> filterStr(List<String> strs, Predicate<String> pre){
ArrayList<String> arrayList = new ArrayList<>();
for (String str : strs) {
// 执行 Lamdba 体
if(pre.test(str)){
arrayList.add(str);
}
}
return arrayList;
}
复制代码
其他类型的函数式接口
- 如你对本文有疑问或本文有错误之处,欢迎评论留言指出。如觉得本文对你有所帮助,欢迎点赞和关注。