现在java已经发展到11了,如果你现在还没有了解lambda表达式的话,那么很幸运,你能看到我的这篇博客。写这篇博客,我由衷地希望能帮助到更多想要学习lambda表达式的人。我力图用最简单的语言,去讲清楚它。
我无不激动地向你介绍这个从java8就已经开始具有的新特性,java程序员新的宠儿,传说中的lambda表达式。它给编程提供了新的方式。虽然用面向对象的的编程都有替代的办法,但是,它实在是太好用了,所以从java8开始,lambda表达式已经被广泛支持了。
那么就请一起来了解一下这个让人无比幸福的语法糖吧。
什么是lambda表达式?它能做什么?如何使用它?
从java诞生以来,面向对象的设计思路让人有一种非常亲切感觉,万物都是对象,我们所做的一切的工作,就是联系对象之间的关系,调用对象的方法,完成各类逻辑。文件是对象,连接是对象,就连class也是对象。面向对象的编程让我们遨游于对象的海洋。一切都非常美好。但是,在我们编程的过程中,我们逐渐发现,一个问题日益凸显而出,这个问题使得我们心里痒痒的,虽然那并不会影响我们的编程,但在编程过程中,它一直让我们觉得不舒服。那就是,我们一直很难把方法当作一个对象(虽然我们有反射,但是在平时,我们不轻易使用它)。如果能够把一个行为作为一个对象,那么在很多时候,把它作为参数传递给别的方法,可以极大的便利我们的编程。在这种背景诉求下,lambda表达式诞生了。
要了解什么是lambda表达式,首先我们要了解什么是表达式。
比如 int a = 2;
String str = "hello world";
以上这些就是我们不能再熟悉的表达式,它们做了这样一件事情:创建一个对象,然后声明了一个变量,然后对那个变量进行赋值。
那么,让我们再试着再大胆一点?
按照我们的希望,我们如果把方法作为对象会怎么样?
Function funtion = public static void hello(){
System.out.println("hello world");
}
哇哦!这看起来太酷了。一个方法被赋值给一个变量!一个方法被赋值给了一个变量???
这是我们一直想要的,这实在是太美好了,这大概就是lambda表达式的雏形?
如果是的话,这让人既难过又开心。开心的是,我们居然能够把方法作为对象,这在以往的java程序里真的是不敢想象,天啊!这逆天的操作!而难过的是,这样写的话,未免也太麻烦了吧?简化!我们必须简化它!
想想看,public是声明调用范围的,public似乎可以去掉?就算要加也应该是加给function的。同样,static,似乎也可以去掉?它是来指明属于类的,还是对象的,就算要加,也应该加给function。
经过修改,现在lambda表达式变成了这样
Function function = void hello(){
System.out.println("hello world");
}
这看起来实在不错。如果以后我们来使用的话,这样的确很简单。
但是能不能更简单?
可以。
真的吗?真的能再简化吗?
想想看,如果一个方法已经被赋值给一个变量了,那么就算这个方法没有方法名,也是无伤大雅的。好,去掉方法名。
而且现在的编译器已经足够聪明,它知道自己能返回什么,那么void也可以去掉。好,去掉void。
然后,我们在()和{}之间,我们加一个->来说明这是一个lambda表达式吧?
所以,最后,lambda表达式变成了这副样子!
Function function = ()->{System.out.println("hello world");};
这实在是让人感觉到无比惊喜,这将是一个伟大的发明。java函数式编程的时代似乎到来了!
但是,事实上,如果你把上面这段代码写在java程序里的话,是报错的= =
Function是什么?java8并没有它的存在。
如果,这能进行赋值的话,Function必须事先声明!
那么该如何声明这个Function呢?
首先,我会告诉你,Function的本质是接口(interface)。
所以,最后程序变成了这个样子。
public class Demo {
public static void main(String[]args){
Function function = ()->{System.out.println("hello world");} ;
}
}
interface Function{
void foo();
}
你一定会很惊讶。什么?Function是接口?你不是说它是方法吗?不是把方法赋值给function变量吗?Function怎么会是接口呢?
别急别急,你慢慢听我说。所以上述程序 可以视为为,把一个输出hello world的lambda表达式赋值给function变量。准确的说,是赋值给Function接口的变量。
那么,它该如何执行呢?其实,如果是以上程序的话,还不会有任何结果。你应该调用foo方法。如下:
public class Demo {
public static void main(String[]args){
Function function = ()->{System.out.println("hello world");} ;
function.foo();
}
}
interface Function{
void foo();
}
没错,这样就可以执行,结果输出了hello world。
有人不禁问,这样到底有什么用。把一句话可以解决的问题硬生生写成5句话,这不是没事找事吗?
没错,在这种地方使用lambda表达式非但不简单,而是更加复杂!
但是,我要说的是,lambda表达式的出现,开启了java函数式编程的时代。而把lambda表达式等价于接口,更是一个高明之举!
为什么?因为,在没有lambda表达式之前。我们想要把一个方法传递给一个方法,往往是把含有该方法的接口传递给那个方法,然后在接口的方法中,完成行为的实现,即实现接口。
比如,下面这段代码你一定不陌生。
public class ThreadExample {
public static void main(String[]args){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(100);
}
});
t.run();
}
}
上面这段代码把Runable接口对象传递给Thread的构造方法作为参数,产生了一个线程类,最后运行了它。
把lambda表达式等价于接口,很大程度上,就是为了避免以上这种方法传递。
设想一下,要把方法A作为参数,传递给另一个方法B。要完成以下几步:第一,要先创建一个含有方法A的接口,第二,把创建好接口传递给那个方法B,第三,在接口中实现那个方法A。如此大费周章的去做一件吃力的事。
而lambda表达式如果可以直接等价于接口的话,那么可以极大的方便了这个操作。
(提示:因为lambda表达式是出于这个目的产生的,所以lambda表达式所对应的接口,只有一个方法!即单方法接口。
换言之,如果一个接口有两个方法,那么没有lambda表达式可以赋值给它。)
好,我们已经知道,lambda表达式的本质是只有一个方法的接口。而Runable接口完全满足这个条件。那么如果把代码转化成lambda表达式,就超级酷了!代码如下:
public class ThreadExample {
public static void main(String[]args){
Thread t = new Thread(()->{System.out.println(100);});
t.run();
}
}
令人窒息的操作!
我们把一个lambda表达式直接当作一个接口,直接传递进Thread的构造方法,相当于等价于Runable这个接口。
我,我已经激动地说不出话了!
有一天,我们学校要做学生信息系统,需要做3件事情。
1.能显示全部学生信息
2.显示带有条件的学生信息(比如姓氏为张的)
3.按照一定条件排序显示学生信息(比如按照年龄排序)
按照需求,我赶紧开始工作。为了简化问题,我假设学生只有序号,姓名,年龄,性别4个属性。
所以,我编写了一个学生类。
public class Student {
private int id;
private String name;
private String sex;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getId() {
return id;
}
public String getName() {
return name;
}
public String getSex() {
return sex;
}
public void setId(int id) {
this.id = id;
}
public void setName(String name) {
this.name = name;
}
public void setSex(String sex) {
this.sex = sex;
}
public Student(int id, String name, String sex, int age) {
this.id = id;
this.name = name;
this.sex = sex;
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", sex='" + sex + '\'' +
", age=" + age +
'}';
}
}
这很容易,然后,我开始编写具体的主程序。
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class StudentExample {
public static void main(String[]args){
List list = Arrays.asList(
new Student(1,"zhangsan","man",22),
new Student(2,"liuqi","woman",21),
new Student(3,"chenliu","woman",19),
new Student(4,"wangwu","man",20),
new Student(5,"lisi","man",19)
);
// show all students
System.out.println("-----------------------------------");
testStudentCondition(list, new Condition() {
@Override
public boolean test(Student student) {
return true;
}
});
System.out.println("-----------------------------------");
// show zhang
testStudentCondition(list, new Condition() {
@Override
public boolean test(Student student) {
return student.getName().startsWith("zhang");
}
});
System.out.println("-----------------------------------");
// sort by age , and show all
list.sort(new Comparator() {
@Override
public int compare(Student student, Student t1) {
return student.getAge()-t1.getAge();
}
});
testStudentCondition(list, new Condition() {
@Override
public boolean test(Student student) {
return true;
}
});
}
public static void testStudentCondition(Listlist,Condition condition){
for(Student student:list){
if(condition.test(student)){
System.out.println(student);
}
}
}
}
interface Condition{
boolean test(Student student);
}
我编写了一个testStudentCondition方法,利用condition对象,可以显示list中满足条件的学生。我对自己写的代码非常满意。
但是,我对这臃肿的接口传递非常不爽。
试试看lambda表达式?
我迫不及待的尝试用它改造我的代码,结果让我惊讶不已:
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
public class StudentExample2 {
public static void main(String[]args){
List list = Arrays.asList(
new Student(1,"zhangsan","man",22),
new Student(2,"liuqi","woman",21),
new Student(3,"chenliu","woman",19),
new Student(4,"wangwu","man",20),
new Student(5,"lisi","man",19)
);
// show all students
System.out.println("-----------------------------------");
testStudentCondition(list,(stu)->true);
System.out.println("-----------------------------------");
// show zhang
testStudentCondition(list,(stu)->stu.getName().startsWith("zhang"));
System.out.println("-----------------------------------");
// sort by age , and show all
list.sort((s1,s2)->s1.getAge()-s2.getAge());
testStudentCondition(list,(stu)->true);
}
public static void testStudentCondition(Listlist,Condition condition){
for(Student student:list){
if(condition.test(student)){
System.out.println(student);
}
}
}
}
我实在没有想到,代码可以写的这样优雅,这种感觉真的是爽呆了!
有一天,我很无聊,想要循环输出学生信息,我用了for循环,还有增强for循环。
代码如下:
import java.util.Arrays;
import java.util.List;
public class StreamDemo { public static void main(String[]args){
List list = Arrays.asList(
new Student(1,"zhangsan","man",18),
new Student(2,"liuqi","woman",19),
new Student(3,"chenliu","woman",20),
new Student(4,"wangwu","man",22),
new Student(5,"lisi","man",21)
);
for(int i=0;i
挺好!是的!
但是,我已经上瘾了,我想要知道,有没有更酷的写法。
比如lambda表达式?
你知道,我为什么喜欢它。
import java.util.Arrays;
import java.util.List;
public class StreamDemo { public static void main(String[]args){
List list = Arrays.asList(
new Student(1,"zhangsan","man",18),
new Student(2,"liuqi","woman",19),
new Student(3,"chenliu","woman",20),
new Student(4,"wangwu","man",22),
new Student(5,"lisi","man",21)
);
list.forEach((stu)->System.out.println(stu));
}
}
天哪!这让人实在满足。
java8以后,list多了一种forEach遍历方式,需要传递的是一个接口,正合我意。
有人不禁会有这样的疑惑。
是不是,要使用lambda表达式,是不是要经常自己声明接口。
我要说的是,完全不用有这种担心。
java专门为来了lambda表达式准备了一个包(java.util.function) !
里面有一些常用的接口,这样我们不需要自己经常写接口,尽情狂欢吧?
比如,我们写的第一个lambda表达式可以不需要声明Condition接口了。
可以变成这样:
import java.util.function.Consumer;
public class Demo {
public static void main(String[]args){
Consumer co = (o)->{System.out.println("hello world");};
co.accept(null);
}
}
Consumer接口,接受一个参数,无返回类型。(值得一提的是,那个o,其实是Object o 忽略参数类型的简写)
lambda表达式的爽口,说几天也说不完,我也没有完全参透。
就先分享到这里。
请一起来享受java程序员的盛宴吧!