一、Lambda表达式引入
这里以对人进行筛选的需求一步步变化来引入lambda
1. 数据准备
定义数据结构
package com.firewolf.java8.s001.lambda.leadin;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 作者:刘兴 时间:2019-10-28
**/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class Person {
/**
* 颜色
*/
private String color;
/**
* 年龄
*/
private Integer age;
}
准备初始数据集合
private List<Person> personList = null;
@Before
public void init() {
personList = Arrays.asList(
new Person("yellow", 30),
new Person("black", 50),
new Person("black", 35),
new Person("white", 45)
);
}
2. 固定条件筛选
我们有一个需求,就是需要找出所有的黑种人,方法如下:
public static List<Person> filterBlackPerson(List<Person> personList) {
List<Person> result = new ArrayList<>();
for (Person person : personList) {
if ("black".equals(person.getColor())) {
result.add(person);
}
}
return result;
}
通过这个方法就可以找出黑种人来
3.抽取变化属性
在2中的代码能够找出黑种人,但是接下来,我们需要查找黄种人,这个时候,上面的代码就不能满足了,所以,我们可以如下写出根据颜色查找的方法
public static List<Person> filterPersonByColor(List<Person> personList, String color) {
List<Person> result = new ArrayList<>();
for (Person person : personList) {
if (color.equals(person.getColor())) {
result.add(person);
}
}
return result;
}
4. 其他属性的处理
接下来,我们需要找出年龄大于某个年龄的人,可以提供如下方法
public static List<Person> filterPersonsByAge(List<Person> personList, int age) {
List<Person> result = new ArrayList<>();
for (Person person : personList) {
if (person.getAge() > age) {
result.add(person);
}
}
return result;
}
5. 一个方法兼容多个属性
我们看到,上面根据颜色和年龄分别查找的时候,需要调用不同的方法,那么,能不能统一到一个方法呢?答案是可以的,如下:
public static List<Person> filterPersons(List<Person> personList, String color, Integer age, Integer filteType) {
List<Person> result = new ArrayList<Person>();
for (Person person : personList) {
if ((filteType==1 && person.getColor().equals(color)) || (filteType==2 && person.getAge() > age)) {
result.add(person);
}
}
return result;
}
我们看到,可以通过一个filteType来区分是根据什么来进行查找
6. 抽象过滤接口
如果我们的人员再增加一个属性,需要怎么处理呢?很容易想到的是,我们在上面接口的基础上在添加分支。然而,如果判断条件发生变化呢?比如年龄是小于,而不是大于怎么办?
解决办法如下:
- 定义一个接口,用于判断是否满足条件
public interface PersonPredicate {
boolean test(Person person);
}
- 接下来,我们可以根据自己的需求定义实现,如:
/**
* 根据肤色过滤器
*/
public static class PersonColorPredicate implements PersonPredicate {
private String color;
public PersonColorPredicate(String color) {
this.color = color;
}
public boolean test(Person person) {
return color.equals(person.getColor());
}
}
/**
* 根据给定的选择器选择人员信息
*/
public static List<Person> filterPersons(List<Person> personList,
PersonPredicate predicate) {
List<Person> result = new ArrayList<>();
for (Person person : personList) {
if (predicate.test(person)) {
result.add(person);
}
}
return result;
}
接下来,如果我们需要根据颜色来判断,可以如下实现:
List<Person> persons1 = PersonUtil.filterPersons(personList, new PersonColorPredicate("yellow"));
System.out.println(persons1);
根据年龄,可以如下调用:
List<Person> persons2 = PersonUtil.filterPersons(personList, new PersonAgePredicate(36));
System.out.println(persons2);
7. 匿名内部类
上面的代码结构已经比较清晰,然而,每增加一种过滤方式,就需要创建这么一个类,如果某个过滤器只是使用一次,那么还需要这么定义的话,会造成很多类的复用性不高,同时增加了类的数量,此时,我们可以通过内部类来解决。
List<Person> persons = PersonUtil.filterPersons(personList, new PersonPredicate() {
@Override
public boolean test(Person person) {
return person.getAge() > 40 && "black".equals(person.getColor());
}
});
System.out.println(persons);
8. lambda表达式
上面的匿名内部类的方式,会让我们能够很灵活的进行处理,然而,会编写一些无用代码,比如方法的声明等等,有没有更简单的办法呢?答案是lambda表达式, 写法如下:
List<Person> persons = PersonUtil.filterPersons(personList, person -> person.getAge() > 40 && "black".equals(person.getColor()));
System.out.println(persons);
这种写法是不是非常简单,也许你现在不太看得懂,不过不要紧,我后面会一步一步展开讲解
二、什么是Lambda表达式
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码 像数据一样进行传递)。可以写出更简洁、更 灵活的代码。作为一种更紧凑的代码风格,使 Java的语言表达能力得到了提升。
例如:
一个Runnable类型的对象创建方式的演变:
java7中使用匿名内部类:
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("hello,world");
}
};
Lambda写法如下:
Runnable r2 = ()-> System.out.println("Hello,World");
可以很明显的看到,使用Lambda表达式的方式会让代码非常的简洁。
lambda特点
- 匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多!
- 函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方 法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
- 传递——Lambda表达式可以作为参数传递给方法或存储在变量中。 简洁——无需像匿名类那样写很多模板代码
三、Lambda语法
Lambda表达式的格式为: 函数式接口 = 参数列表 -> Lambda体
,
参数列表:就好比反复 add(int a , int b) ,那么int a, int b 就是参数列表;
Lambda体:可以理解为函数体
函数式接口:这个在下一章详细讲解
(一) "->"操作符
- 在Java8中引入了一个新的操作符号“->”,该操作符被称为箭头操作符或者是Lambda操作符
- 左侧:Lambda表达式的参数列表
- 右侧:表达式所需要执行的功能,称为Lambda体。
(二)几种语法形式
- 语法格式一:无参数,无返回值
public void test1() { Runnable r = () -> System.out.println("Hello,Lambda"); r.run(); }
- 语法格式二:有一个参数,没有返回值
public void test2() { Consumer<String> c = (x) -> System.out.println("Hello," + x); c.accept("Baby"); }
- 语法格式三:当只有一个参数的时候,可以省略掉参数的小括号
public void test3() { Consumer<String> c = x -> System.out.println("Hello," + x); c.accept("Test3"); }
- 语法格式四:有两个及以上个数参数,并且有返回值,需要使用{}把lambda体包起来
public void test3() { Consumer<String> c = x -> System.out.println("Hello," + x); c.accept("Test3"); }
- 语法格式五:有返回值,但是lambda体只有一条语句时,可以省略{}和return
public void test5() { BinaryOperator<Long> op = (x, y) -> x + y; }
- 语法格式六:可以指明参数类型,默认情况下,可以不指定。注意的是,如果要指明,就都得指明
public void test6() { BinaryOperator<Integer> op = (Integer x, Integer y) -> x + y; }
四、Lambda类型推断
- 在Lambda表达式中的参数类型可以由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译;
- 这是因为 javac 根据程序的上下文,在后台 推断出了参数的类型
- Lambda 表达式的类型依赖于上 下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”
五、注意事项
在Java7之前,匿名内部类如果访问了同级的变量,那么需要把这个变量使用final修饰,而在lambda中如果引用了同级别的变量,不需要用final修饰,但是不能改变其值,其实就是Java8内部自己加上的final修饰符,不用咱们自己显示添加