前言
lambda表达式现在应用得越来越多,以前我总觉得lambda表达式可读性是不是不太好,但是用了它之后,真香。一直想找个时间写篇文章总结一下,但是平时比较忙,就一直没时间写,从周一就立了一个flag,周末一定要写,但是周六又默默地打开了王者荣耀,唉,还一波连跪,就很离谱。抓住周末的尾巴,可算把flag完成了。
一、什么是lambda表达式
lambda表达式是一个匿名函数,我们可以将其理解为一段可以传递的代码。利用它我们可以写出更加简洁、灵活的代码。
二、lambda表达式初体验
我们通过一个简单的例子来体验一下lambda表达式。
需求:将学生按照年龄的大小进行排序。
import lombok.Data;
@Data
public class Student{
private String name;
private Integer age;
private Integer score;
}
传统的Java代码:
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
Comparator<Student> comparator = new Comparator<Student>() {
@Override
public int compare(Student o1, Student o2) {
return o1.getAge() - o2.getAge();
}
};
Set<Student> students = new TreeSet<>(comparator);
for (int i = 5; i > 0; i--) {
students.add(new Student("test"+i,18+i,90));
}
for (Student student : students) {
System.out.println(student);
}
}
}
简单的说一下Comparator比较器,它用以自定义排序规则,其中的compare方法接受两个比较的对象,如果第一个参数大于第二个参数,返回正数,等于返回0,小于返回负数。
通过上面传统的Java代码我们可以看到,对于我们开发人员阅读来说,其实compare方法,仅仅只有一行代码是有用的。但是传统的Java代码却占用了6行,可以想象一下,如果程序中包含大量的类似情景,就会使得我们一个程序异常庞大。而对于Java规范来说,一个方法的方法体是不宜过长的。现在,我们使用lambda表达式对上述代码进行优化。
在这之前,我们需要知道Java的函数式接口:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。详细介绍见末尾。
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
public class Test {
public static void main(String[] args) {
//使用lambda表达式
Comparator<Student> comparator = (x,y) -> x.getAge()-y.getAge();
Set<Student> students = new TreeSet<>(comparator);
for (int i = 5; i > 0; i--) {
students.add(new Student("test"+i,18+i,90));
}
for (Student student : students) {
System.out.println(student);
}
}
}
我们可以看到,使用lambda表达式之后,由原来的6行代码,变成了一行。
我们再通过一个需求,一步步的优化代码,体验lambda给我们带来的好处。
需求:找出年龄大于15岁的学生。
public class Test {
private static List<Student> students = new ArrayList<>();
/**
* 初始化学生列表
*/
static {
students.add(new Student("张三",20,85));
students.add(new Student("李四",14,95));
students.add(new Student("王五",22,100));
students.add(new Student("李华",17,77));
}
/**
*
* @param objStudent 需要查找的学生列表
* @return 返回的学生列表
*/
//找出年龄大于15岁的学生
public static List<Student> filterStudent(List<Student> objStudent){
List<Student> result = new ArrayList<>();
for (Student student : objStudent) {
if(student.getAge() > 15){
result.add(student);
}
}
return result;
}
public static void main(String[] args) {
List<Student> students = filterStudent(Test.students);
System.out.println(students);
}
}
现在需求变了,我想找出分数大于80的学生。
可能很多初学者会这样实现,将filterStudent()再次拷贝一份,将if(student.getAge() > 15)变为if(student.getScore() > 80),那将来增加无数的需求,都需要增加一个方法吗?
其实我们分析一下,需求变了,对于filterStudent方法的大致逻辑是不变的。变的仅仅只是判断条件,那么我们是否可以将判断条件抽取出来,在需要使用这个方法的时候,将判断的逻辑通过参数传递?实现如下:
public interface Filter {
/**
* 过滤方法,由使用者去实现
* @param student
* @return
*/
boolean filter(Student student);
}
改造之后的filterStudent方法如下:
/**
*
* @param objStudent 需要查找的学生列表
* @param filter 过滤规则
* @return
*/
public static List<Student> filterStudent(List<Student> objStudent, Filter filter){
List<Student> result = new ArrayList<>();
for (Student student : objStudent) {
if(filter.filter(student)){
result.add(student);
}
}
return result;
}
现在,我们如何去使用它呢?
public static void main(String[] args) {
//找出年龄大于15岁的学生
Filter filterAge = (student) -> student.getAge() > 15;
List<Student> resultAge = filterStudent(Test.students,filterAge);
System.out.println(resultAge);
//找出分数大于80分的学生
Filter filterScore = (student) -> student.getScore() > 80;
List<Student> resultScore = filterStudent(Test.students,filterScore);
System.out.println(resultScore);
}
好了,通过优化以后,我们已经可以应对各种各样的筛选需求。
可能有经验的编程人员会发现上述的代码有一些小瑕疵,就是Filter接口的filter方法并不通用,为啥呢?filter方法中我们传入的对象写死为Student类型,这个实在不好,所以,我们使用泛型将其改进一下,完整代码如下:
public interface Filter<T> {
/**
* 过滤方法,由使用者去实现
* @param
* @return
*/
boolean filter(T t);
}
public class Test {
private static List<Student> students = new ArrayList<>();
/**
* 初始化学生列表
*/
static {
students.add(new Student("张三",20,85));
students.add(new Student("李四",14,95));
students.add(new Student("王五",22,100));
students.add(new Student("李华",17,77));
}
/**
*
* @param objStudent 需要查找的学生列表
* @param filter 过滤规则
* @return
*/
public static List<Student> filterStudent(List<Student> objStudent, Filter<Student> filter){
List<Student> result = new ArrayList<>();
for (Student student : objStudent) {
if(filter.filter(student)){
result.add(student);
}
}
return result;
}
public static void main(String[] args) {
//找出年龄大于15岁的学生
Filter<Student> filterAge = (student) -> student.getAge() > 15;
List<Student> resultAge = filterStudent(Test.students,filterAge);
System.out.println(resultAge);
//找出分数大于80分的学生
Filter<Student> filterScore = (student) -> student.getScore() > 80;
List<Student> resultScore = filterStudent(Test.students,filterScore);
System.out.println(resultScore);
}
}
这种设计方法有着广泛的运用,大家在编程中适当的多多运用这种方法,来提升代码的复用性。
三、lambda表达式结构
lambda表达式可分为三部分:lambda参数列表、lambda操作符、lambda体。() -> {}
根据参数列表不同,方法体的行数不同,lambda可以有不同的变种。
语法一:无参数、无返回值
public interface NoParamNoReturn {
void print();
}
public static void main(String[] args) {
//如果lambda体中只有一行代码,{}可以省略,
//NoParamNoReturn no = ()-> {System.out.println("Hello Lambda");};
NoParamNoReturn no = ()-> System.out.println("Hello Lambda");
no.print();
}
语法格式二:有一个参数,无返回值
public interface ILambda {
void print(Student student);
}
public static void main(String[] args) {
//如果只有一个参数()可以省略不写,(x)-> System.out.println(x.getName());
ILambda no = x -> System.out.println(x.getName());
no.print(new Student("张三",20,20));
}
语法格式三:有一个参数,多条语句,有返回值.
public interface ILambda {
boolean print(Student student);
}
public static void main(String[] args) {
ILambda no = x -> {
System.out.println(x.getName());
return true;
};
no.print(new Student("张三",20,20));
}
语法格式四:有一个参数,一条语句,有返回值.
public static void main(String[] args) {
// return 可以省略不写,ILambda no = x -> {return x.getAge()>15;};
ILambda no = x -> x.getAge() > 15;
no.print(new Student("张三",20,20));
}
这些变种无需强行记忆,多用,多看,自然能够孰能生巧。初学者只需按照标准语法写即可。
四、函数式接口
在上文中,我们提到了函数式接口,也使用到了函数式接口,我们回顾一下函数式接口的定义:函数式接口(Functional Interface)就是一个有且仅有一个抽象方法,但是可以有多个非抽象方法的接口。函数式接口可以被隐式转换为 lambda 表达式。
在Java中为我们提供了四大函数式接口:
1、消费型函数接口
Consumer<T>
void accept(T t);
该接口接受一个T类型的参数,无返回值。
需求:统计学生C语言成绩和Java语言成绩总分。
@Data
@Data
public class Student1 {
private String name;
private Integer age;
private Integer javaScore;
private Integer cScore;
private Integer totalSocre;
}
public static void main(String[] args) {
Consumer<Student1> consumer = student -> student.setTotalSocre(student.getJavaScore()+student.getCScore());
Student1 student = new Student1();
student.setJavaScore(80);
student.setCScore(90);
consumer.accept(student);
System.out.println(student);
}
2、供给型接口
Supplier<T>
T get();
该接口无参数,返回一个T类型的对象
需求:产生随机数
public static void main(String[] args) {
Supplier<Integer> sup = () -> (int)(Math.random() *100);
System.out.println(sup.get());
}
3、函数型接口
Function<T, R>
R apply(T t);
接受一个T类型的参数,返回一个R类型。
需求,获取该学生的总分数
public static void main(String[] args) {
Function<Student1,Integer> function = student -> student.getCScore() + student.getJavaScore();
Student1 student = new Student1();
student.setJavaScore(80);
student.setCScore(90);
Integer totalScore = function.apply(student);
System.out.println(totalScore);
}
4、断言型接口
Predicate<T>
boolean test(T t);
给定一个T,返回一个Boolean
需求:找出年龄大于15岁的学生。
其实这个需求,我们已经实现了,只是将Filter接口换为Predicate接口即可。
public class Test {
private static List<Student> students = new ArrayList<>();
/**
* 初始化学生列表
*/
static {
students.add(new Student("张三",20,85));
students.add(new Student("李四",14,95));
students.add(new Student("王五",22,100));
students.add(new Student("李华",17,77));
}
/**
*
* @param objStudent 需要查找的学生列表
* @param predicate 过滤规则
* @return
*/
public static List<Student> filterStudent(List<Student> objStudent, Predicate<Student> predicate){
List<Student> result = new ArrayList<>();
for (Student student : objStudent) {
if(predicate.test(student)){
result.add(student);
}
}
return result;
}
public static void main(String[] args) {
Predicate<Student> predicate = x -> x.getAge() > 15;
List<Student> students = filterStudent(Test.students, predicate);
System.out.println(students);
}
}
好了,本文到此处就结束了,相信大家对lambda表达式已经有了很清楚的认识,感谢阅读。