Java8 新特性之Lambda表达式
1. Lambda 表达式概述
- Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码(将代码像数据一样进行传递);
- Lambda 表达式可以写出更简洁,更灵活的代码;
// Lambda 表达式入门程序
public class TestLambda{
List<Employee> employees = Arrays.asList(
new Employee("张三",16,1000),
new Employee("李四",22,2000),
new Employee("熊大",25,1300),
new Employee("熊二",32,1200),
new Employee("赵六",43,3200)
);
@Test
public void test2(){
List<Employee> list = filterEmpByAge(employees);
for(Employee emp : list){
System.out.println(emp);
}
}
// 需求一: 获取当前公司中,员工年龄大于等于30的员工信息
public List<Employee> filterEmpByAge(List<Employee> list){
List<Employee> emps = new ArrayList<>();
for(Employee emp : list){
if(emp.getAge() >= 30){
emps.add(emp);
}
}
return emps;
}
// 需求二: 获取当前公司中,员工工资大于等于2000的员工信息
public List<Employee> filterEmpBySal(List<Employee> list){
List<Employee> emps = new ArrayList<>();
for(Employee emp : list){
if(emp.getSalary() >= 2000){
emps.add(emp);
}
}
return emps;
}
// 优化方式一: 策略设计模式
@Test
public void test3(){
// 按年龄过滤
List<Employee> list = filterEmployee(employees,new FilterEmployeeByAge());
for(Employee employee : list){
System.out.println(employee);
}
// 按工资过滤
List<Employee> list2 = filterEmployee(employees,new FilterEmployeeBySal());
for(Employee employee : list2){
System.out.println(employee);
}
}
public List<Employee> filterEmployee(List<Employee> list, MyPredicte<Employee> mp){
List<Employee> emps = new ArrayList<>();
for(Employee employee : list){
if(mp.test(employee)){
emps.add(employee);
}
}
}
// 优化方式二: 匿名内部类
// 不再需要创建泛型接口的实现类
@Test
public void test4() {
// filterEmployee(employees, 匿名内部类);
List<Employee> list = filterEmployee(employees, new MyPredicate<Emplooyee>(){
public boolean test(Employee emp){
return emp.getSalary() <= 2000;
}
});
for(Employee employee : list){
System.out.println(employee);
}
}
// 优化方式三: Lambda 表达式
@Test
public void test5(){
List<Employee> list = filterEmployee(employees, (e) -> e.getSalary() <= 2000);
list.forEach(System.out::println);
}
// 优化方式四: Stream API
@Test
public void test6(){
employees.stream()
.filter((e) -> e.getSalary() <= 2000)
.forEach(System.out::println);
// 获取所有员工的名字
employees.stream()
.map(Employee::getName)
.forEach(System.out::println);
}
}
// 创建泛型接口
public interface MyPredicate<T>{
public boolean test(T t);
}
// 创建泛型接口的实现类
public class filterEmpByAge implements MyPredicate<Employee> {
public boolean test(Employee emp){
return emp.getAge() >= 30;
}
}
public class filterEmpBySal implements MyPredicate<Employee> {
public boolean test(Employee emp){
return emp.getSalary() >= 2000;
}
}
2. Lambda 表达式基础语法
->
: 该操作符称为箭头操作符或Lambda 操作符,将Lambda 表达式拆分成两部分:- 左侧: Lambda 表达式的参数列表;
- 右侧: Lambda 表达式所需执行的功能,即 Lambda 体;
- 语法格式一: 无参数,无返回值
() -> System.out.println("Hello Lambda!");
- 语法格式二: 有一个参数,无返回值;
(x) -> System.out.println(x);
- 或者
x -> System.out.println(x);
若只有一个参数,小括号可以省略不写;
- 语法格式三: 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句;
- Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器可以通过上下文推断出数据类型,即"类型推断";
(Integer x, Integer y) -> Integer.compare(x,y);
其中,数据类型 Integer,可以省略不写;
// 测试类
public class TestLambda{
@Test
public void test01(){
int num = 0; // JDK 1.7 之前,必须是final,才能在匿名函数内部使用
// JDK 1.8 可以不使用 final, 但是在匿名函数内部仍然不能进行运算
// 无参数,无返回值
// 1. Java8 之前写法:
Runnable r = new Runnable(){
public void run(){
System.out.println("Hello World!" + num);
}
};
r.run();
// Lambda 表达式
Runnable r2 = () -> System.out.println("Hello World!");
r2.run();
}
@Test
public void test02(){
// 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator<Integer> com = (x,y) -> {
System.out.println("函数式接口");
return Integer.compare(x,y);
};
// 有两个以上的参数,有返回值,Lambda 体中只有一条语句; 大括号和return可以省略
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
}
}
3. Lambda 表达式需要"函数式接口"的支持
- 函数式接口:接口中只有一个抽象方法时,称为函数式接口;
- 可以使用注解
@FunctionalInterface
进行修饰,这样,可以检查该接口是否是函数式接口;
// Lambda 练习
// 声明一个带两个泛型的函数式接口,泛型类型为<T,R>, T 为参数, R 为返回值;
// 接口中声明对应抽象方法;
// 在 TestLambda 类中声明方法,使用接口作为参数,计算两个 long 型参数的和与乘积
// 函数式接口
@FunctionalInterface
public interface MyFunction<T,R>{
public R getValue(T t1, T t2);
}
// 测试类
public class TestLambda{
public void option(Long l1, Long l2, MyFunction<Long,Long> mf){
System.out.println(mf.getValue(l1,l2));
}
@Test
public void test(){
option(100L,200L,(x,y) -> x+y);
option(100L,200L, (x,y) -> x*y);
}
}
4. 四大内置核心函数式接口
- 消费型接口:
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)
5. 方法引用
- 方法引用: 若 Lambda 体中的内容有方法已经实现了,我们可以使用"方法引用";
可以理解为,方法引用是 Lambda 表达式的另外一种表现形式; - 主要有三种语法格式:
对象::实例方法名
类::静态方法名
类::实例方法名
- 注意: Lambda 体中调用方法的参数列表与返回值类型,要与函数式接口中抽象方法的函数列表和返回值类型保持一致!
- 注意2: 若 Lambda 参数列表中的第一个参数是实例方法的调用者,而第二个参数是实例方法的参数时,可以使用
ClassName::method
方式调用;
5.1 构造方法引用
ClassName::new
- 注意: 需要调用的构造器参数列表要与函数式接口中抽象方法的参数列表保持一致!
5.2 数组引用
Type::new
public class TestMethodRef{
@Test
public void test(){
Consumer<String> con = (x) -> System.out.println(x);
con.accept("测试方法....");
//对象::实例方法名
Consumer<String> con2 = System.out::println;
con2.accept("测试方法");
// 示例二: 获取员工姓名
Employee emp = new Employee();
Supplier<String> sup = () -> emp.getName();
String str = sup.get();
System.out.println(str);
Supplier<String> sup2 = emp::getName;
String str2 = sup2.get();
System.out.println(str2);
}
@Test
public void test2(){
// 类::静态方法名
Comparator<Integer> com = (x,y) -> Integer.compare(x,y);
Comparator<Integer> com2 = Integer::compare;
}
@Test
public void test3(){
// 类::实例方法名
BiPredicate<String,String> bp = (x,y) -> x.equals(y);
Bipredicate<String,String> bp2 = String::equals;
}
@Test
public void test4(){
Supplier<Employee> sup = () -> new Employee();
// 构造器引用(无参构造器)
Supplier<Employee> sup2 = Employee::new;
Function<Integer,Employee> fun = (x) -> new Employee(x);
// 构造器引用(有一个参数的构造器)
Function<Integer,Employee> fun = Employee::new;
}
@Test
public void test5(){
Function<Integer,String[]> fun = (x) -> new String[x];
String[] strs = fun.apply(10);
System.out.println(strs.length);
// 数组方式的引用
Function<Integer,String[]> fun2 = String[]::new;
}
}
参考资料