JDK8 发布至今已经8年,且马上就要9年了。而我们今天要聊的 Lambda 就是当年 JDK8 推出时伴随引入而来的新特性之一,也是个人认为,JDK8 新特性中最值得去学习之一 相信你肯定或多或少都会听说过或者看到过甚至是使用过 Lambda表达式 的这一种写法,也或许有的小伙伴是第一次听说这个。无所谓,这些都不重要,即使以前不曾听闻,相信你也能在看完这篇文章之后,能够对 Lambda 有一个了解,甚至是在你的代码上渐渐使用起来~
那么在我们正式开始之前,先来简单的聊一聊这个 Lambda 到底是一个什么来的?
其实 Lambda 是一种计算机的编程语言 ,而我们实际上要去用的,Lambda表达式 却是一个匿名函数 。这两者之间是不同的,一个是语言;一个是一种方式,一种代码风格,可别搞混了。
那么或许有人会问了,我们为什么要去使用 Lambda表达式 呢?Lambda表达式 将函数式编程的概念引入了 Java,而函数式编程的好处在于可以帮助我们大大的节省代码量;可以对接口进行一个非常简洁的实现。
或者换句话说,可以让你的代码,变得十分简洁,变得十分优雅~
java@Test
public void test09(){
TreeMap<Integer, Object> map = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
map.put(1, "天龙八部");
map.put(3, "神雕侠侣");
map.put(5, "射雕英雄传");
map.put(9, "侠客行");
map.put(8, "鹿鼎记");
map.put(6, "笑傲江湖");
map.forEach((k,v) -> System.out.println(k + " -- " + v));
}
单元测试跑起来后,我们可以看到 TreeMap 根据我们定义的规则去排序了。感兴趣的可以试一下没有排序是什么样子的,然后去对比一下

javaTreeMap<Integer, Object> map = new TreeMap<>((o1, o2) -> o2 - o1);
虽然我们可以使用 Lambda表达式 对接口进行某些极为简洁的操作,但不是所有的接口都能够使用 Lambda表达式 的,Lambda表达式 对于接口的要求:接口中定义的必须要实现的方法只能是一样
这个是时候就可以引入一个 Java.lang 包下的一个注解:@FunctionalInterface ,这个注解用于修饰函数式的接口,即用了该注解在接口上之后,会自动去检查有且仅有一个抽象方法
如果有看了 Comparator接口 源码的朋友就会发现,在 Comparator 的源码中加了 @FunctionalInterface 这个注解,但它有两个抽象方法,为什么呢?

@FunctionalInterface 注解里面给出了解释:大概的意思就是如果这个抽象方法是 Object 的公共方法之一,那么就不计入接口抽象方法计数中;且 JDK8 中加入了默认方法的特性,可以有一个或多个默认方法。

感兴趣的可以去了解一下,总而言之看到这个注解,那么就意味着可以去使用 Lambda表达式 去进行书写了
首先我们先来看看 Lambda表达式 的基础语法:
对,就这三个,玩来玩去也就是玩这几个了符号了,虽然似乎Lambda 还有一些别的语法,但基础语法就这三个,玩明白这三个,就差不多能够玩懂 Lambda 了
废话不多说,直接开始
(1) 无返回、无入参
那么要写 Lambda,就肯定少不了函数式接口了,所以我们自己去整一个:
java@FunctionalInterface
public interface TsWithoutReturnAndParam {
// 没有返回值、没有入参
void test();
}
按照我们以往正常的写法,就是实现接口,重写接口的方法,然后再去执行对应的 test() 方法;或者类似像上面 Comparator 例子中用匿名函数重写里面的方法去实现。这两种方式都是没有使用到 Lambda表达式 的,那么用 Lambda表达式 怎么写呢?拭目以待
java@Test
public void test10(){
TsWithoutReturnAndParam lambda = new TsWithoutReturnAndParam() {
@Override
public void test() {
System.out.println("飞雪连天射白鹿,笑书神侠倚碧鸳");
}
};
lambda.test();
}
java @Test
public void test10(){
TsWithoutReturnAndParam lambda = () -> {
System.out.println("飞雪连天射白鹿,笑书神侠倚碧鸳");
};
lambda.test();
}
java @Test
public void test10(){
TsWithoutReturnAndParam lambda = () -> System.out.println("飞雪连天射白鹿,笑书神侠倚碧鸳");
lambda.test();
}
(2) 无返回、单个入参
继续整一个新接口
java@FunctionalInterface
public interface TsNoReturnSingleParam {
// 没有返回值、单个入参
void test(int param);
}
java @Test
public void test11(){
TsNoReturnSingleParam lambda = (r) -> {
System.out.println("你的入参为:" + r);
};
lambda.test(8);
}
java @Test
public void test11(){
TsNoReturnSingleParam lambda = (r) -> System.out.println("你的入参为:" + r);
lambda.test(8);
}
java @Test
public void test11(){
TsNoReturnSingleParam lambda = r -> System.out.println("你的入参为:" + r);
lambda.test(8);
}
(3) 无返回、多个入参 继续整:
java@FunctionalInterface
public interface TsNoReturnMultiParam {
/*
// 多参,多个入参嘛~ 本来应该是这个的,但这与我想要演示的不一致,所以后面再演示这种吧,这个先不看
void test(int... params);
*/
void test(int param1, int param2);
}
多参嘛~两个也算是多参对吧!但其实我注释掉的才更严谨些,没关系,就当多看了一种演示了。因为我想要演示的,是不能去掉括号的情况,就与我 Comparator 那个例子中遍历 map 集合那样,括号中有两个参数
java @Test
public void test12(){
TsNoReturnMultiParam lambda = (p1, p2) -> System.out.println("第一个参数:" + p1 + " " + "第二个参数:" + p2);
lambda.test(6, 7);
}
在多个参数的时候,或者说是两个、及两个以上的时候,(),这个表示参数列表的符号还是需要保留的
好了,继续看有返回的
(4) 有返回、单个入参
或许有人会问,有返回、无入参去哪了?其实这个我原本也写出来了,不过感觉没啥营养,就把它干掉了。直接从单个入参开始吧,感兴趣的可以自己去试试
继续整:
java@FunctionalInterface
public interface TsSingleReturnSingleParam {
int test(int param);
}
当我们的函数式接口有返回值的时候,会稍微有些不一样:
java @Test
public void test13(){
TsSingleReturnSingleParam lambda = r -> {
System.out.println("你输入的参数是:" + r);
return 88;
};
int result = lambda.test(1);
System.out.println("返回值 = " + result);
}
可以看到,在方法体中有两个代码,所以 {} 就不能去掉了;那么如果只有一行代码的时候呢?按照前面的理解,是不是就应该变成:TsSingleReturnSingleParam lambda = r -> return 88 ,其实这个是不行的,这样就会报错,编译不通过了
在我们方法体中,如果唯一的一条语句是一条返回语句,那么在省略大括号的同时,还需要省略 return ,所以,应该变成这样:TsSingleReturnSingleParam lambda = r -> 88
注意...我上面的返回值是直接写死了 “88” 的,可能会造成一个误导。其实有返回、无入参,便是这样的一个形式,写死一个数在返回那里,你写什么就返回什么。所以这就没有什么意义了,所以这一段可以写成这样:
java @Test
public void test13(){
TsSingleReturnSingleParam lambda = r -> r;
int result = lambda.test(5566);
System.out.println("返回值 = " + result);
}
(5) 有返回、多个入参
继续看:
java@FunctionalInterface
public interface TsSingleReturnMultiParam {
String test(int... params);
}
在上面的 无参、多个入参 看了两个入参的演示,那么现在来瞅瞅真正的多个入参是什么样子的
int... 像类似这种写法,可以经常在源码中看到。这是 JDK1.5 新增的语法,表示动态参数的意思,其实本质上也还是一个数组。所以可以把它当成一个数组处理即可
java @Test
public void test14(){
TsSingleReturnMultiParam lambda = params -> Arrays.toString(params);
String result = lambda.test(1, 3, 5, 7, 9);
System.out.println("返回值 = " + result);
}
因为就像上面提到的 params 是一个数组,所以转成字符串放回出去就可以看到结果了。
所以到这里,想必你也大概能理解他的优化过程了。那么上面提到的 Comparator 就应该知道它是怎样一下子变成了那样吧?小伙伴可以尝试一下把优化的过程一步步写出来~
java @Test
public void test12(){
/*
// 最开始的样子,没有使用Lambda表达式
TreeMap<Integer, Object> map = new TreeMap<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
*/
/*
// 使用Lambda表达式
TreeMap<Integer, Object> map = new TreeMap<>((Integer o1, Integer o2) -> {
return o2 - o1;
});
*/
/*
// 优化1:方法体中只有一行,{} 干掉
TreeMap<Integer, Object> map = new TreeMap<>((Integer o1, Integer o2) -> o2 - o1);
*/
// 优化2:参数中的基本数据类型是可以省略的,基本数据类型干掉
TreeMap<Integer, Object> map = new TreeMap<>((o1, o2) -> o2 - o1);
}
到这里,想必对 Lambda表达式 也已经有了一个大致的了解了。其实很简单的,就是围绕着 () 、-> 、{} 这三个而衍射出来的不同玩法,大大的减少了代码量,从而变得简洁、美观。但个人认为,这些都不重要,最重要的是,用了 Lambda表达式 之后,代码变得十分优雅~
毕竟优雅永不过时~
好了,这里就对上面的一些用法做一个总结吧:
当参数列表,即 () 为单参时,可以忽略 ()
当方法体,即{}里面的代码只有一行时,也可以忽略 {}
但,如果方法体中有返回值,在忽略 {} 时,return 也需去掉
即不管 () 、{} 这两个存在还是忽略,-> 这个箭头符号还是必须有的
看到这里了,相必你对 Lambda表达式 的使用也有了一定的了解了。那么接下来,我们在来看一下 Lambda表达式 的一些进阶玩法。
直接来看语法:方法的隶属者 :: 方法名
那么什么叫 方法的隶属者 呢?划重点:如果一个方法是静态方法,那么隶属者就是类了;而如果不是静态方法,那么隶属者就是当前的对象了
用到的一个符号就是
::两个冒号了,就类似是一个指向的意思了。可以快速的将一个 Lambda表达式 的实现指向一个已经实现的方法,这种写法在使用 MyBatis-Plus 操作单表时用的极多,是的,极多!
简而言之的说呢...就是...我们来看一个例子:
javapublic class Quote {
public static int operation(int param) {
System.out.println("入参为:" + param);
return param * 2;
}
}
类名.方法名 然后传参从而得到一个 int 类型的返回值java @Test
public void test19(){
int result = Quote.operation(8);
System.out.println(result);
}
TsSingleReturnSingleParam 与当前的定义的静态方法 operation 返回值一样,所以可以进行一个引用。java @Test
public void test19(){
// 使用Lambda表达式
TsSingleReturnSingleParam lambda = r -> operation(r);
System.out.println("lambda = " + lambda.test(8));
}
注意:被引用的方法的参数数量以及类型需与接口中定义的抽象方法一致;且返回值类型也需要和接口中定义的一致
java @Test
public void test19(){
// Lambda表达式进阶
TsSingleReturnSingleParam lambda = Quote::operation;
System.out.println("lambda = " + lambda.test(8));
}
语法 ---- 方法的隶属者 :: 方法名,方法的隶属者:Quote;方法名:operation;
注意,不是一定要静态方法,普通的方法也是可以的。就像前面说的,如果一个方法是静态方法,那么隶属者就是类了;而如果不是静态方法,那么隶属者就是当前的对象了。而只要满足语法的前提下,都是可行的。
这个也是一样的,归根结底还是方法的引用,毕竟构造方法也还是方法嘛。
(1) 创建一个 Book 类
javapublic class Book {
private String bookName;
private String author;
public Book() {
System.out.println("无参构造被调用...");
}
public Book(String bookName, String author) {
System.out.println("有参构造被调用...");
this.bookName = bookName;
this.author = author;
}
// getter、setter等常规方法省略
}
(2) 创建两个接口,一个用于调用无参构造的;一个用于调用有参构造的
java@FunctionalInterface
public interface BookCreateNoParam {
Book getBook();
}
java复制代码@FunctionalInterface
public interface BookCreateWithParam {
Book getBook(String bookName, String author);
}
java @Test
public void test20(){
// 无参构造
BookCreateNoParam lambda1 = () -> new Book();
System.out.println("Book = " + lambda1.getBook());
// 有参构造
BookCreateWithParam lambda2 = (name, author) -> new Book(name, author);
System.out.println("Book = " + lambda2.getBook("笑傲江湖", "金庸"));
}
new 来表示构造函数,即:java @Test
public void test20(){
// 无参构造
BookCreateNoParam lambda1 = Book::new;
System.out.println("Book = " + lambda1.getBook());
// 有参构造
BookCreateWithParam lambda2 = Book::new;
System.out.println("Book = " + lambda2.getBook("笑傲江湖", "金庸"));
}
Book::new 的形式去创建对象;而至于你是有参还是无参,在接口中便定义了,在调用接口中的抽象方法时传递参数在开始之前,可能要引入两篇我前面的写的文章了:
化繁为简,MP 里面的增删改查:juejin.cn/post/716717…
化繁为简,MP 中条件构造器的使用:juejin.cn/post/716874…
因为演示的实体类我就不重新去搭建了,直接使用之前两篇文章中的一些例子来改造吧
Lambda表达式 主要是在 MP 中的条件构造器中使用的比较多,
java @Test
public void test10() {
QueryWrapper<User> qw = new QueryWrapper<>();
qw.lambda().eq(User::getAge, 18).eq(User::getName,"逍遥");
List<User> userList = userMapper.selectList(qw);
userList.forEach(System.out::println);
}
(1) 此时的隶属者就是:User 对象,而 getAge、getName 是里面的方法,所以可以直接去引用
(2) 只有下面的 forEach 那里,一般用于对集合遍历的,这个对象需要传一个 Comsumer 接口实现,而这个 Consumer又是一个函数式接口,源码如下:

(3) 其中的抽象方法 accept 就相当于把集合中每一个对象都扔进去进行操作,至于你的操作是什么,自己定义。这个与我们前面定义的 TsNoReturnSingleParam 类似,只是我们的返回值类型是 int 而这里是一个可变的 T
(4) System.out::println 这句就是 System 这类,里面有一个静态方法 out ,静态方法 out 里面又有一些方法,所以 :: 引用,和前面一个意思;且同时代码只有一行,然后那些可以有可以没有的都给干掉了,就剩下一句了
(5) 就比如我们上面 User::getName ,那么我们写成 User.getName() 不一样可以。所以 System.out.println() 与 System.out::println 是一个意思。只是一个是以前的写法,一个是 JDK8 的写法
java @Test
public void test12() {
LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
LambdaQueryWrapper<User> qw = new LambdaQueryWrapper<>();
qw.like(User::getName, "月")
.eq(User::getAge,18)
.orderByDesc(User::getCreateTime);
List<User> userList = userMapper.selectList(qw);
userList.forEach(System.out::println);
}
(1) LambdaQueryWrapper 像之前文章介绍的,你也可以直接把这个实现个 new 出来,这种就是 Lambda表达式 的写法了。
(2) 当然,你也可以像上面那里的写法,通过 QueryWrapper 的实现类去点 lambda() 也是可以转换成 Lambda表达式 的写法,两种都可以,无关紧要
(3) 这里的这段就是,模糊查询名字中包含 "月" 的,并且年龄为 18 岁的,然后根据创建时间排序
当然,实际业务肯定是又长又臭的,如果没有使用 Lambda表达式 的方式的话代码量可能就会比较多了,一坨一坨的;而使用 Lambda表达式 则会简洁许多,稍微优雅一些
什么!你不信?我这里有一个小例子
(1) 传统的方式
java @Test
public void test15(){
User user1 = new User();
user1.setId(0);
user1.setName("");
user1.setAge(0);
user1.setMobile("");
user1.setEmail("");
user1.setCreateTime(LocalDateTime.now());
user1.setCreateBy("");
user1.setUpdateTime(LocalDateTime.now());
user1.setUpdateBy("");
user1.setIsDelete(0);
}
(2) 简洁的方式(这种较为优雅)
java @Test
public void test15(){
User user2 = new User().setId(0)
.setName("")
.setAge(0)
.setMobile("")
.setEmail("")
.setCreateTime(LocalDateTime.now())
.setCreateBy("")
.setUpdateTime(LocalDateTime.now())
.setUpdateBy("")
.setIsDelete(0);
}
显然,第二种更为简洁些,少了很多冗余的代码。而且看着也极为优雅。
这个是 Lombok 中的 @Accessors(chain = true) 注解的使用,感兴趣的可以去搜一下相关的文章。
好了,Lambda 这一块也差不多了,相信看到这里,小伙伴们也都差不多能够熟悉了解 Lambda表达式 的使用了。 那么接下来就是 JDK8 中 Stream流 的使用,还是那句话:优化永不过时让我们优雅到底吧 敬请期待
作者:天怎么不会塌 链接:https://juejin.cn/post/7171056274488950815 来源:稀土掘金 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
本文作者:wucc
本文链接:
版权声明:引用:从基础到精通,一遍文章读懂 JDK8 Lambda表达式 的使用! 标题:从基础到精通,一遍文章读懂 JDK8 Lambda表达式 的使用! - 掘金 网址:https://juejin.cn/post/7171056274488950815