Lambda表达式总结
文章目录
一. Lambda简介与用途
Lambda 表达式,也可称为闭包,它是推动 Java 8 发布的最重要新特性。Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
可以取代大部分的匿名内部类,写出更优雅的 Java 代码,尤其在集合的遍历和其他集合操作中,可以极大地优化代码结构。JDK 也提供了大量的内置函数式接口供我们使用,使得 Lambda 表达式的运用更加方便、高效。
二. 函数式接口
- 接口要求:只能有一个需要被实现的方法,可以有多个default方法和static方法
- 显示定义函数式接口注解@FunctionalInterface,当然如果满足函数式接口的要求,不用加注解,是隐式定义
三. Lambda基础语法
jdk8引入了一个新的操作符 -> 称之为箭头操作符 或者叫Lambda操作符
lambda需要函数式接口的支持
口诀:复制小括号,写死右键头,落地大括号
接口:
/**多参数无返回*/
@FunctionalInterface
public interface NoReturnMultiParam {
void method(int a, int b);
}
/**无参无返回值*/
@FunctionalInterface
public interface NoReturnNoParam {
void method();
}
/**一个参数无返回*/
@FunctionalInterface
public interface NoReturnOneParam {
void method(int a);
}
/**多个参数有返回值*/
@FunctionalInterface
public interface ReturnMultiParam {
int method(int a, int b);
}
/*** 无参有返回*/
@FunctionalInterface
public interface ReturnNoParam {
int method();
}
/**一个参数有返回值*/
@FunctionalInterface
public interface ReturnOneParam {
int method(int a);
}
调用实现:
public class Test1 {
public static void main(String[] args) {
//无参无返回
NoReturnNoParam noReturnNoParam = () -> {
System.out.println("NoReturnNoParam");
};
noReturnNoParam.method();
//一个参数无返回
NoReturnOneParam noReturnOneParam = (int a) -> {
System.out.println("NoReturnOneParam param:" + a);
};
noReturnOneParam.method(6);
//多个参数无返回
NoReturnMultiParam noReturnMultiParam = (int a, int b) -> {
System.out.println("NoReturnMultiParam param:" + "{" + a +"," + + b +"}");
};
noReturnMultiParam.method(6, 8);
//无参有返回值
ReturnNoParam returnNoParam = () -> {
System.out.print("ReturnNoParam");
return 1;
};
int res = returnNoParam.method();
System.out.println("return:" + res);
//一个参数有返回值
ReturnOneParam returnOneParam = (int a) -> {
System.out.println("ReturnOneParam param:" + a);
return 1;
};
int res2 = returnOneParam.method(6);
System.out.println("return:" + res2);
//多个参数有返回值
ReturnMultiParam returnMultiParam = (int a, int b) -> {
System.out.println("ReturnMultiParam param:" + "{" + a + "," + b +"}");
return 1;
};
int res3 = returnMultiParam.method(6, 8);
System.out.println("return:" + res3);
}
}
四. Lambda重要特性
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值,但是必须所有参数都不写
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
public static void main(String[] args) {
//1.简化参数类型,可以不写参数类型,但是必须所有参数都不写
NoReturnMultiParam lamdba1 = (a, b) -> {
System.out.println("简化参数类型");
};
lamdba1.method(1, 2);
//2.简化参数小括号,如果只有一个参数则可以省略参数小括号
NoReturnOneParam lambda2 = a -> {
System.out.println("简化参数小括号");
};
lambda2.method(1);
//3.简化方法体大括号,如果方法条只有一条语句,则可以省略方法体大括号
NoReturnNoParam lambda3 = () -> System.out.println("简化方法体大括号");
lambda3.method();
//4.如果方法体只有一条语句,并且是 return 语句,则可以省略方法体大括号
ReturnOneParam lambda4 = a -> a+3;
System.out.println(lambda4.method(5));
ReturnMultiParam lambda5 = (a, b) -> a+b;
System.out.println(lambda5.method(1, 1));
}
五. Lambda表达式使用实例
有一个学生的实体类,有年龄,姓名,收入三个属性
class Student{
int age;
String name;
int many;
public Student(int age, String name, int many) {
this.age = age;
this.name = name;
this.many = many;
}
.....重新toString get set方法
问题:求出年龄大于18的学生
1 . 常规做法是
public static List<Student> filter(List<Student> students){
List<Student> empstudents=new ArrayList<>();
for (Student s:students) {
if(s.getAge>18){
empstudents.add(s);
}
}
return empstudents;
}
但是存在一个问题,这里的判断条件写死了,如果我现在改需求,求出收入大于6000的学生,那么我这个方法就就需要改,不够通用,而且代码量一旦多起来,维护起来就很困难。
所以如果我们能将过滤条件也当作参数进行传递的话,每次更改需求,只需要在调用这个方法的时候传递条件这个参数就可以了。这里就是运用了设计模式中的策略模式-----策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换 。
2. 策略模式做法
一:定义一个函数式接口
@FunctionalInterface
public interface myFilter {
public boolean test(Student student);
}
二:定义方法,将过滤条件也作为参数
public static List<Student> filter(List<Student> students,myFilter mfilter){
List<Student> empstudents=new ArrayList<>();
for (Student s:students) {
if(mfilter.test(s)){
empstudents.add(s);
}
}
return empstudents;
}
}
三:使用Lamdal,实现接口中方法,传递过滤条件
List<Student> studentList = Arrays.asList(new Student(12, "张三", 6000),
new Student(20, "李四", 7000),
new Student(19, "王五", 8000),
new Student(30, "赵六", 9000));
//需求:求出many大于7000的
List<Student> students = filter(studentList, (e) -> e.getMany() > 7000);
students.forEach(System.out::println);
3. 内置函数式接口Predicate
做到这里我们发现每次使用函数接口都需要重新定义一个接口很麻烦。其实Jdk提供了内置的函数式接口供我们使用。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
.......
所以我们可以直接使用这个内置的接口,实现test方法。需要注意这个接口构造函数需要传入类型,不然不能推断出参数类型。
public static List<Student> filter(List<Student> students, Predicate<Student> mfilter){
List<Student> empstudents=new ArrayList<>();
for (Student s:students) {
if(mfilter.test(s)){
empstudents.add(s);
}
}
return empstudents;
}
4. 调用Stream API
studentList.stream()
.filter(e->e.getMany()>7000)
.forEach(System.out::println);
5. 注意几点
- 使用Lamdal,小括号中参数类型可以省略,Lambda可以做类型推断
- Lambdal简化了匿名内部类
- Lambdal可以称之为闭包,可以将一段代码作为参数进行传递
六. 匿名内部类或Lambda使用局部变量
lambda表达式可以使用局部变量,但是必须是final类型的或事实上final类型的(不可改变).
<<java8实战>>中的解释:
第一,实例变量和局部变量背后的实现有一个关键不同。实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问它的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制。
第二,这一限制不鼓励你使用改变外部变量的典型命令式编程模式(我们会在以后的各章中
解释,这种模式会阻碍很容易做到的并行处理)。