# Lambda 表达式
# 前言
Lambda 是一个匿名函数,我们可以把 Lambda 表达式理解为是一段可以传递的代码。
它可以让我们在 Java 8 的环境下写出更简洁、更灵活的代码。
作为一种更紧凑的代码风格,使 Java 的语言表达能力得到了一定的提升。
如果你学过ECMAScript6
简称ES6
语法,那么你会发现 Lambda 和它的箭头表达式的确是有点相像的。
# 学习动力
我相信有很多的朋友,因为觉得 Lambda 代码难以阅读,且大部分人觉得没有学习的必要,当公司有同事在使用时,则心里暗暗烦躁,代码读起来极度难以理解。
相信你能看到这里,证明你已经被困扰过了,没错,我就是这样走过来的,希望在这里能让你学好 Lambda 。
# 学前场景案例
# 简单的例子
首先我们看一个例子:
Runnable rb = new Runnable() {
@Override
public void run(){
System.out.println("浅梦博客");
}
};
rb.run();
2
3
4
5
6
7
上述例子中是一个简单的最原始的线程执行方法,如果我们使用Lambda表达式,可以达到如下效果:
Runnable rb = () -> System.out.println("浅梦博客");
rb.run();
2
是不是看起来简洁多了?这就是Lambda最为直观的魅力。
# 学生案例
现在我们有了这么些信息:
List<Student> students = Arrays.asList(
new Student(101, "小明", 12, 88.88),
new Student(102, "小杰", 13, 66.66),
new Student(103, "浅梦", 27, 99.99),
new Student(104, "中杨", 10, 77.77),
new Student(105, "大健", 5, 55.55)
);
2
3
4
5
6
7
然后我们现在有一个需求:
需求:获取学生中年龄小于等于 12 的学生信息
public List<Student> filterStudentAge(List<Student> students){
List<Student> list = new ArrayList<>();
for (Student student : students) {
if (student.getAge() <= 12){
list.add(student);
}
}
return list;
}
2
3
4
5
6
7
8
9
上述书写了一个过滤方法,把年龄小于等于12的学生对象返回出去,在需要的地方我们可以直接调用了
List<Student> list = this.filterStudentAge(students);
for (Student student : list) {
System.out.println(student);
}
2
3
4
这是最简单也是大家一般都能想到的方法,毫无美感可言,可以看出我这种是为了完成需求而工作。
当需求发生改变时,比如我又需要获取成绩大77
的学生信息,是不是又要写一个filterStudentAge
方法呢?
然后我们就会将这些代码进行优化。
# 学生案例优化一:策略设计模式
不得不说,我们伟大的设计模式,能为我们解决很多问题,就比如这个学生案例的需求。
现在我们来改造一下:
首先定义一个接口:
public interface MyHandler<T> {
boolean test(T t);
}
2
3
定义两个需求实现:
public class FilterStudentForAge implements MyHandler<Student> {
@Override
public boolean test(Student student) {
return student.getAge() <= 12;
}
}
2
3
4
5
6
public class FilterStudentForScore implements MyHandler<Student> {
@Override
public boolean test(Student student) {
return student.getScore() > 77.00;
}
}
2
3
4
5
6
定义一个功能方法:
public List<Student>filterStudent(List<Student> students , MyHandler<Student> myHandler){
List<Student> list = new ArrayList<>();
for (Student student : students) {
if(myHandler.test(student)){
list.add(student);
}
}
return list;
}
2
3
4
5
6
7
8
9
具体需求执行过滤:
@Test
public void test(){
// 获取年龄小于等于12的学生信息
List<Student> list = this.filterStudent(students, new FilterStudentForAge());
for (Student student : list) {
System.out.println(student);
}
System.out.println("------------------");
// 获取成绩大于77的学生信息
List<Student> list2 = this.filterStudent(students, new FilterStudentForScore());
for (Student student : list2) {
System.out.println(student);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
可以看到,上述的例子中,运用了策略设计模式,这样每当我们新加需求,只需要创建一个实现类即可。
# 学生案例优化二:匿名内部类
在上述例子中不知道有没有看出来一些弊端,就是当每次有新的需求时,我们每次都要创建一个实现类。
那么如果我有20多个需求,是不是就需要创建20多个类来完成需求了?
此时我们使用匿名内部类来简化一下,不再创建实现类来完成需求了。
@Test
public void test() {
// 获取 id 小于等于 103 的学生信息
List<Student> list = filterStudent(students, new MyHandler<Student>() {
@Override
public boolean test(Student t) {
return t.getId() <= 103;
}
});
for (Student student : list) {
System.out.println(student);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
可以看到,我们直接在调用时new
一个MyHandler
接口,并直接进行接口的方法重写进行实现。
# 学生案例优化三:Lambda表达式
可以在简洁一点吗?完全可以的,因为我们Java 8
给我们提供了Lambda
表达式,也是本文所述的主要知识点。
@Test
public void test6() {
// 获取大于等于13岁的学生信息
List<Student> list = filterStudent(students, student -> student.getAge() >= 13);
for (Student student : list) {
System.out.println(student);
}
}
2
3
4
5
6
7
8
是不是感觉到了Lambda表达式的强大之处呢?省略了多少代码?省略了多少个实现类?
虽然难懂一点。
记住,写难懂的代码才能让你在公司拥有不可替代性
# Lambda 语法
Java 8中引入了一个新的操作符 ->
该操作符称为箭头操作符 或 Lambda 操作符。
箭头操作符将 Lambda 表达式拆分成两部分:
左侧:Lambda 表达式的参数列表
右侧:Lambda 表达式中所需执行的功能,意指Lambda体。
在实际场景应用中,存在着三种语法格式。
# 一、无参数,无返回值
语法
() -> System.out.println("Hello Lambda!");
Demo
@Test
public void test(){
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("Hello World!");
}
};
r.run();
}
2
3
4
5
6
7
8
9
10
Lambda
当我们使用一个Runnable
时,它是一个接口,该接口提供了一个无参方法,并且是无返回值的,使用Lambda
表达式可以达到如下效果。
@Test
public void test2(){
Runnable r1 = () -> System.out.println("Hello Lambda!");
r1.run();
}
2
3
4
5
# 二、有一个参数,无返回值
语法
(x) -> System.out.println(x);
Demo
@Test
public void test(){
Consumer<String> con = new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
};
con.accept("Hello World!");
}
2
3
4
5
6
7
8
9
10
Lambda
使用Comsumer
消费接口,它有一个有参方法,但是无返回值,可以得到如下效果。
@Test
public void test2(){
Consumer<String> con = s -> System.out.println(s);
con.accept();
}
2
3
4
5
细心的你应该留意到小括号不翼而飞了,这是一个语法糖。
小括号当只有一个参数时,可以省略不写。
# 三、两个参数,有返回值,多条执行语句
语法
(x, y) -> {
System.out.println("xxx");
xxx.xxx(x, y);
};
2
3
4
Demo
@Test
public void test() {
Comparator<Integer> com = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
System.out.println("xxx");
return Integer.compare(o1, o2);
}
};
}
2
3
4
5
6
7
8
9
10
Lambda
Comparator<Integer> com = (x, y) -> {
System.out.println("xxx");
Integer.compare(x, y);
};
2
3
4
# Lambda 函数式接口
Lambda 表达式需要 函数式接口 的支持。
# 函数式接口是什么?
函数式接口,即一个接口中只有一个抽象方法,成为函数式接口。
我们通常会在函数式接口上使用注解 @FunctionalInterface
修饰,以规范并检查该类是否为函数式接口。
# 四大核心函数式接口
一、 消费型接口
public interface Consumer<T> {
void accept(T t);
}
2
3
二、供给型接口
public interface Supplier<T> {
T get();
}
2
3
三、函数型接口
public interface Function<T, R> {
R apply(T t);
}
2
3
四、断言型接口
public interface Predicate<T> {
boolean test(T t);
}
2
3
上述的四大核心接口是 Java 8 提供给我们的,也是日常当中使用最多的,可作用于各种应用场景。
比如消费型接口:
@Test
public void Test(){
this.buyComputer("Mac Book Pro");
}
public void buyComputer(String computer){
this.insertStr("我买了一台:", str -> System.out.println(str + computer));
}
public void insertStr(String string, Consumer<String> con){
con.accept(string);
}
2
3
4
5
6
7
8
9
10
又比如供给型接口:
@Test
public void test(){
List<Integer> numList = getNumList(10, () -> (int)(Math.random() * 100));
for (Integer num : numList) {
System.out.println(num);
}
}
//需求:产生指定个数的整数,并放入集合中
public List<Integer> getNumList(int num, Supplier<Integer> sup){
List<Integer> list = new ArrayList<>();
for (int i = 0; i < num; i++) {
Integer n = sup.get();
list.add(n);
}
return list;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
至于函数型接口和断言型接口就不再举例了,请发挥你想象力。
# Lambda 方法引用
若 Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用。
我们可以将方法引用理解为 Lambda 表达式的另外一种表现形式。
它可以为我们提供最为直观的方法调用。
需要注意的是:
方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
# 语法
类名::MethodName
# 示例1
Consumer<String> con = System.out::println;
con.accept("Hello Java 8");
2
可以看出,这是一个最简单的例子,又比如这样:
public class TestService {
public String show(){
return "调用了 TestService 的 show 方法!";
}
}
2
3
4
5
TestService testService = new TestService();
Supplier<String> sup = testService::show;
System.out.println(sup.get()); // 打印 调用了 TestService 的 show 方法!
2
3
# 示例2
首先我们有一个测试的service类。
public class TestService {
public String show(){
return "调用了 TestService 的 show 方法!";
}
}
2
3
4
5
然后我们来一个方法的引用
@Test
public void test11(){
System.out.println("step1--------------------------------");
Function<TestService, String> fun = new Function<TestService, String>() {
@Override
public String apply(TestService testService) {
return testService.show();
}
};
String apply = fun.apply(new TestService());
System.out.println(apply);
System.out.println("step2--------------------------------");
Function<TestService, String> fun2 = item -> item.show();
String apply2 = fun2.apply(new TestService());
System.out.println(apply2);
System.out.println("step3--------------------------------");
Function<TestService, String> fun3 = TestService::show;
String apply3 = fun3.apply(new TestService());
System.out.println(apply3);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
在上述的例子当中它们的执行结果是一样的,例子中从上到下做了三部分:
第一部分,就是我们最熟悉的匿名内部类了。
第二部分,使用了lambda
表达式进行了优化。
重点是第三部分,可以看出省去了 item -> item.show();
的写法,直接以类名TestService::show
省去了书写参数、小括号、箭头等的代码,看上去更加的优雅。
简单的语句,蕴含着这么多东西,这就是为什么你曾经看别人写 Lambda
觉得特别难懂的原因!
# 示例3
最后再看另外一个例子:
BiPredicate<String, String> bp = (x, y) -> x.equals(y);
System.out.println(bp.test("123a", "123a"));
System.out.println("-----------------------------------------");
BiPredicate<String, String> bp2 = String::equals;
System.out.println(bp2.test("abc", "abc"));
2
3
4
5
6
7
在上述的例子当中,我已经能够预知你目前是一脸懵逼的状态,没关系,在看一下图解吧。

细心的你一定发现这其中的规律,当你的函数式接口提供的唯一方法的参数列表有2个,且有返回值时,并且你执行的方法体的方法参数只接收一个值时,可以使用第一个参数的参数类型::方法体
来进行书写。

# 理解
通过上述的例子,你应该大致理解了什么是 Lambda
的方法引用。
这是一个慢慢习惯的过程,在方法引用上,大致可以分为如下几类:
- 对象的引用
::
实例方法名 - 类名
::
静态方法名 - 类名
::
实力方法名
它其实只不过是一个语法糖,给予我们最优的解决了当流程比较复杂时,可以使用简洁的Lambda来实现这些流程。
其实最常见的也只不过是为了省略写方法时的小括号而已吧。
# 构造器引用
Lambda
也为我们提供了构造器的引用写法。
需要注意的是:构造器的参数列表,需要与函数式接口中参数列表保持一致!
# 语法
类名::new
# 示例
还是以一个示例来的痛快。
public class TestService {
public TestService(String val){
System.out.println("Java8 " + val);
}
}
2
3
4
5
@Test
public void test(){
Function<String, TestService> fun = TestService::new;
fun.apply("我爱你"); // 打印 Java8 我爱你
}
2
3
4
5
上述例子证明,调用Function.apply
方法后,它调用了TestService
的构造方法,同时将Function
的apply
的参数传递给了构造器的val
参数,从而达到这个效果。
本文由浅梦博客www.starmcc.com原创,转载请注明出处,请尊重别人的劳动成果,谢谢。
← Java 8 新特性 Stream API →