1、lambda表达式简介

Lambda表达式是Java 8 添加的一个新的特性,简单来说,Lambda据视一个匿名函数

2、为什么使用Lambda?

使用Lambda表达式可以对接口进行一个非常简洁的实现。

举一个例子,编写一个compare接口,这个接口接受两个参数,并比较传入参数的大小。

1
2
3
interface Compare {
int compare(int a,int b);
}

对于上面的这个接口,我们有多种方式实现接口中的compare方法

2.1、编写一个compare接口的实现类

1
2
3
4
5
6
class MyCompare implements Compare {
@Override
public int compare(int a, int b) {
return a - b;
}
}

2.2、使用匿名内部类

1
2
3
4
5
6
Compare compare = new Compare() {
@Override
public int compare(int a, int b) {
return a - b;
}
};

2.3、使用Lambda表达式

1
2
//使用Lambda表达式
Compare lambdaCompare = (a,b) -> a - b;

以上三种实现方式完全相同。

3、Lambda对接口的要求

虽然我们可以使用Lambda表达式对 某些 接口进行简单实现,但是并不是所有接口都可以使用Lambda表达式来实现。

Lambda对接口要求为:接口中定义的必须要实现的方法只能是一个。

@FunctionalInterface

这个注解用于修饰函数式接口,即只有一个抽象方法的接口。

4、Lambda基础语法

4.1、前期准备

创建一系列接口,以便后面的学习

  • 编写一个无参数无返回值的函数式接口
1
2
3
4
@FunctionalInterface
public interface LambdaWithourReturnAndParam {
void test();
}
  • 编写一个只有一个参数且不带返回值的函数式接口
1
2
3
4
@FunctionalInterface
public interface LambdaNoneReturnSingleParam {
void test(int a);
}
  • 编写一个有多个参数没有返回值的接口
1
2
3
4
@FunctionalInterface
public interface LambdaNoneReturnMultiParam {
void test(int a,int b);
}
  • 编写一个没有参数有返回值的接口
1
2
3
4
@FunctionalInterface
public interface LambdaSingleReturnNoneParam {
int test();
}
  • 编写一个带单个参数,含返回值的接口
1
2
3
4
@FunctionalInterface
public interface LambdaSingleReturnSingleParam {
int test(int a);
}
  • 编写一个带多个参数,含返回值的接口
1
2
3
4
@FunctionalInterface
public interface LambdaSingleReturnMultiParam {
int test(int a,int b);
}

上面的接口均用@FunctionalInterface注解进行修饰

下面使用Lambda表达式对上面接口进行一一实现

4.2、基础语法

Lambda是一个匿名函数,我们需要关注的点有 返回值类型参数列表方法体

在Lambda表达式中,返回值类型我们可以不用显式的写出来。

  • () : 用于描述参数列表
  • {} : 用于描述方法体
  • -> : 用于分隔参数列表和方法体,是Lambda表达式的运算符,读作goes to

4.3、使用Lambda表达式实现无参无返回接口

1
2
3
4
LambdaWithourReturnAndParam lambda1 = () -> {
System.out.println("芜湖起飞");
};
lambda1.test();

运行,查看结果

image-20210222234526505

注意:当 {} 中定义的方法体只有一行代码时,{} 可以被省略。

即上面的代码可以简化为

1
2
LambdaWithourReturnAndParam lambda1 = () -> System.out.println("芜湖起飞");
lambda1.test();

4.4、使用Lambda表达式实现单个参数无返回值接口

1
2
LambdaNoneReturnSingleParam lambda2 = (a) -> System.out.println("传入的参数为:" + a);
lambda2.test(1);

结果

image-20210222234844579

4.5、使用Lambda表达式实现多个参数无返回值接口

1
2
LambdaNoneReturnMultiParam lambda = (a, b) -> System.out.println("传入的参数a为:" + a + ",b为:" + b + "和为:" + (a + b));
lambda.test(1,2);

结果

image-20210222235042067

4.6、使用Lambda表达式实现无参有返回接口

1
2
3
4
5
LambdaSingleReturnNoneParam lambda = () -> {
System.out.println("这是无参带返回值接口的实现");
return 100;
};
System.out.println(lambda.test());

结果

image-20210222235340354

4.7、使用Lambda表达式实现单参有返回接口

1
2
3
4
5
LambdaSingleReturnSingleParam lambda = (a) -> {
System.out.println("这是单参带返回值接口的实现,传入的参数为:" + a);
return 100;
};
System.out.println("得到的返回值为:" + lambda.test(3));

结果

image-20210222235512243

4.8、使用Lambda表达式实现多参有返回接口

1
2
3
4
5
LambdaSingleReturnMultiParam lambda = (a,b) -> {
System.out.println("这是多参带返回值接口的实现,传入的参数和为:" + (a + b));
return a + b;
};
System.out.println("得到的返回值为:" + lambda.test(3,5));

结果

image-20210223000257626

5、Lambda语法精简

5.1、参数精简

由于在接口的抽象方法中,已经定义了参数的数量和类型,所以在Lambda表达式中,参数类型可以省略。

即 Lambda lambda = (String a,String b) -> return (a + b);

可以简写为Lambda lambda = (a,b) -> return (a + b);

  • 注意:如果要省略参数类型,那么每一个参数的类型都必须省略。

5.2、小括号精简

如果在参数列表中只有一个参数,那么小括号也可以省略不写

即 Lambda lambda = (a) -> System.out.println(a);

可以简写为: Lambda lambda = a -> System.out.println(a);

5.3、大括号精简

如果方法体中只有一句代码,那么大括号可以省略不写

即 Lambda lambda = (a) -> {

​ System.out.println(a);

};

可以简写为: Lambda lambda = a -> System.out.println(a);

  • 注意:如果方法体中唯一的一条语句是一条返回语句,那么在省略大括号的同时,还需要省略return

  • 例如

1
2
LambdaSingleReturnMultiParam lambda = (a,b) -> a + b;
System.out.println("得到的返回值为:" + lambda.test(3,5));
  • 结果为

image-20210223002012212

6、Lambda语法进阶

6.1、方法引用

可以快速地将一个lambda表达式的实现指向一个已经实现的方法。

语法:方法的隶属者::方法名

例如:传入一个参数,要求返回传入参数的2倍

  • 原来的写法
1
2
LambdaSingleReturnSingleParam lambda = a -> a * 2;
System.out.println(lambda.test(2));

使用方法引用之后的写法

  • 定义一个静态方法,这个方法的方法体可以有多行代码
1
2
3
4
private static int change(int a) {
System.out.println("芜湖起飞");
return a * 2;
}
  • 在lambda表达式的方法体中引用上面写的静态方法
1
2
3
//方法引用
LambdaSingleReturnSingleParam lambda = a -> change(2);
System.out.println(lambda.test(2));
  • 执行结果

image-20210223002908069

6.2、使用 方法隶属者::方法名 的方式实现方法引用

方法隶属者:如果一个方法是静态方法,那么其隶属者就是类,如果不是静态方法, 那么隶属者就是对象。

根据方法引用的语法,上面的方法引用可以改写为

1
2
3
//方法引用
LambdaSingleReturnSingleParam lambda = Syntax2::change;
System.out.println(lambda.test(2));

注意点:

  • 被引用的方法的参数数量和类型一定要和接口中定义的抽象方法一致。
  • 返回值的类型也一定要和接口中定义的一致。

6.3、构造方法引用

  • 新建一个类Person
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Person {
public String name;
public int age;

public Person() {
System.out.println("Person类的无参构造函数执行了...");
}

public Person(String name, int age) {
System.out.println("Person类的有参构造函数执行了...");
this.name = name;
this.age = age;
}
}
  • 创建类Syntax3,在里面创建一个接口
1
2
3
interface PersonCreater {
Person getPerson();
}

构造方法引用的应用场景:在某些情况下,我们返回了某个类的对象

使用关键字 new 来代表构造函数

1
PersonCreater creater2 = Person::new;
  • 测试
1
2
PersonCreater creater2 = Person::new;
creater2.getPerson();
  • 结果

image-20210223004633194

注意:构造函数的参数由接口中的参数来自定义,如果接口中的抽象方法是一个有参的抽象方法,那么就可以使用有参构造函数,否则只能使用无参构造函数

  • 创建一个PersonCreater2接口
1
2
3
interface PersonCreater2 {
Person getPerson(String name,int age);
}
  • 引用有参构造函数
1
2
PersonCreater2 creater2 = Person::new;
Person person = creater2.getPerson("芜湖",18);
  • 结果

image-20210223005323172

结论:**不管引用的是有参构造函数还是无参构造函数,在引用的过程中一律使用 方法隶属名::方法名 ** ,有参构造方法的参数在调用接口抽象方法时传递

7、Lambda综合案例

7.1、集合排序

已知在一个ArrayList中,有若干个Person对象,将这些Person对象按照年龄进行降序排序

我们需要使用到ArrayList对象中的sort函数,这个函数需要我们传入一个Comparator实现类对象,Comparator是一个函数式接口。

image-20210223010154107

Comparator 中的 compare方法

image-20210223010612662

集合排序的代码如下:

1
2
3
4
5
6
7
8
9
10
ArrayList<Person> persons = new ArrayList<>();
persons.add(new Person("A",10));
persons.add(new Person("B",999));
persons.add(new Person("C",7));
persons.add(new Person("D",6));
persons.add(new Person("E",14));
persons.add(new Person("F",18));
//降序要求
persons.sort((p1,p2) -> p2.age - p1.age);
persons.stream().forEach(person -> System.out.println(person));

为Person添加一个toString方法,测试并查看结果

image-20210223011055014

7.2、TreeSet

创建一个TreeSet对象,往对象中添加元素

1
2
3
4
5
6
7
8
TreeSet set = new TreeSet();
set.add(new Person("A",10));
set.add(new Person("B",999));
set.add(new Person("C",7));
set.add(new Person("D",6));
set.add(new Person("E",14));
set.add(new Person("F",18));
System.out.println(set);
  • 运行查看结果

image-20210223011530846

这是因为TreeSet对象存储的值是有序的,而对于我们传入的泛型Person,TreeSet不知道如何使其有序,所以抛出了一个类型转换异常。

如何解决这个问题?解决方法有二:

  • 让Person类实现 Comparator 接口,并实现compare方法

  • 在构造TreeSet对象时,传入一个Comparator实现对象

  • TreeSet构造方法如下

image-20210223011854725

在构造TreeSet时传入一个Comparator实现对象

1
2
3
4
5
6
7
8
TreeSet<Person> set = new TreeSet<Person>((p1,p2) -> p2.age - p1.age);
set.add(new Person("A",10));
set.add(new Person("B",999));
set.add(new Person("C",7));
set.add(new Person("D",6));
set.add(new Person("E",14));
set.add(new Person("F",18));
set.stream().forEach(person -> System.out.println(person));
  • 查看测试结果

image-20210223012117318

7.3、集合遍历

使用 集合对象.foreach 方法遍历集合,这个对象需要传入一个Comsumer接口实现

image-20210223012712632

而Comsumer又是一个函数式接口,accept方法的逻辑是将集合中的每一个对象都导入到方法accept中。

image-20210223012744761

遍历集合,输出集合中的每一个元素

list.foreach(System.out::println);

1
2
3
4
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,999,4396,7777,2800,999,888);
//遍历集合
list.forEach(System.out::println);
  • 结果

image-20210223012951109

我们也可以在遍历元素的过程中添加一些自己的逻辑代码,例如:遍历集合并输出其中的偶数

1
2
3
4
5
6
7
8
ArrayList<Integer> list = new ArrayList<>();
Collections.addAll(list,1,2,3,999,4396,7777,2800,999,888);
//遍历集合
list.forEach(ele -> {
if(ele % 2 == 0) {
System.out.println(ele);
}
});
  • 结果

image-20210223013135333

结论:在使用lambda表达式进行foreach循环的过程中,-> 符号前的变量就是每次循环时的元素,而 {} 就是执行的方法体,当方法体只有一行代码时,{}可以省略。