目录



随着Java语言的不断发展,Java8提供的新特性lambda表达式也成为越来越多开发者喜欢的写法,为了顺应时代的变化,需要好好地学习这种写法,并应用在平时的开发中。我们先从简单的例子着手,一步步的深入到lambda的复杂写法,首先我们的任务是创建一个用来比较两个int值大小的接口,并给它添加具体的实现方法,然后进行调用。接口定义如下:

interface Comparator{
int compare(int a,int b);
}


常规写法

写法一:新建类并实现该接口

创建MyComparator.java并实现Comparator接口:

class MyComparator implements Comparator{
@Override
public int compare(int a, int b) {
return a > b ? a : b; //输出最大的值
}
}


然后进行调用:

public static void main(String[] args) {
Comparator comparator = new MyComparator();

int res = comparator.compare(1, 10);
System.out.println(res);
}


写法二:使用匿名函数

直接new接口

public static void main(String[] args) {
//使用匿名方法实现
Comparator comparator = new Comparator() {
@Override
public int compare(int a, int b) {
return a > b ? a : b;
}
};

int res = comparator.compare(1, 10);
System.out.println(res);
}


lambda写法

跟上面的常规写法不同的是,lambda只关注方法的入参方法体中具体实现的逻辑,其余能省的代码全部都可以省略,我们先看一下第一种写法:

写法一:lambda常规写法

自己比较一下跟匿名函数写法的差别,是不是非常的简洁,lambda只声明了入参方法体内的逻辑,关键地方在于入参和方法体之间使用了​​->​​箭头符号进行连接,箭头符号读作​​gose to​​。

public static void main(String[] args) {
//简单的lambda写法
Comparator comparator = (int a,int b) -> {
return a > b ? a : b;
};

int res = comparator.compare(1, 10);
System.out.println(res);
}


到这里你应该有一个基本的印象了,这一步转变非常重要,停下来好好思考一下。

写法二:lambda简写

仔细观察可以发现写法一中仍然有多余的代码,比如说我们在之前的接口中已经声明过一次入参的类型,在使用的时候又声明了一次,这显然是多余的写法。其实上面的写法依然可以更简洁,只要满足下面这几点,都可以省略。

  1. 当入参类型和初始定义类型一致时,可以省略入参类型。
  2. 当方法体中只有一句代码时,可以省略大括号。
  3. 当方法体中只有一句代码,并且需要返回值时,可以省略​​return​​关键字。

更简单的写法如下:

public static void main(String[] args) {
//省略了入参类型、大括号、return关键字
Comparator comparator = (a,b) -> a > b ? a : b;

int res = comparator.compare(1, 10);
System.out.println(res);
}


中场疑问

上面我们定义的接口只有一个抽象方法​​compare()​​,有的同学就会有疑问,那如果有多个抽象方法怎么写呢?问的很好!由于lambda的写法非常的简洁,但这是在一定的条件限制下才能这么写,其中非常重要的一点就是,只有一个抽象方法的接口才支持lambda的写法,这种接口被称为函数式接口,可以通过​​@FunctionalInterface​​注解进行修饰。

@FunctionalInterface只修饰函数式接口,即只有一个抽象方法的接口。

lambda的方法引用

写法一

lambda表达式中方法体只有一行代码才能有最简单的写法,那如果我想写一些复杂的业务就不能简写了吗?其实并不是的,这时候我们就可以泳道lambda的方法引用,将较复杂的业务封装成方法,然后在lambda中进行调用,假如还是上面的例子,我们可以将方法体中的那一行代码封装成一个方法:

public static int max(int a,int b){
//这里可以写一些较为复杂的业务
return a > b ? a : b;
}


这时候lambda表达式就可以这么写:

public static void main(String[] args) {
//方法体中引用max函数
Comparator comparator = (a,b) -> max(a,b);
int res = comparator.compare(1, 10);
System.out.println(res);
}


上面就是一个普通的lambda方法引用,简单的说就是将方法体中的代码封装成一个一方法,然后在调用这个方法即可,需要注意的是max函数中的返回值必须要跟接口中的抽象方法一致,否则就会报异常。

写法二

上面的写法虽然简单有效,但依然有多余的地方,那就是方法参数重复了,代码​​(a,b) -> max(a,b);​​中出现了两次​​(a,b)​​,在实际开发中,我们一般采用另一种写法来代替​​->​​箭头符号,请大家牢记语法:

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

隶属者可以理解为,能够调用这个方法的对象,如果该方法是​​static​​修饰的,则隶属者位当前类;如果不是​​static​​修饰的,则隶属者属于一个对象。如果听不懂也没关系,看一个栗子就清晰明了。

首先看一下完整的代码:

//定义接口
interface Comparator {
int compare(int a, int b);
}

public class Program {
public static void main(String[] args) {
//使用lambda的方法引用调用max方法,隶属者为类
Comparator comparator = Program::max;
int res = comparator.compare(1, 10);
System.out.println(res);
}
//用static修饰的方法
public static int max(int a,int b){
return a > b ? a : b;
}
}


从上面代码中我们发现,将之前的​​(a,b) -> max(a,b);​​写成了​​Program::max;​​,因为方法​​max​​被关键字​​static​​修饰了,因此隶属者应该是​​Program​​这个类。也可以发现省略了参数​​(a,b)​​,这么写就已经是非常的简洁了,但这么写需要满足两个基本条件:

  1. 只有接口中抽象方法的参数格式跟定义的方法参数格式一样,才能省略参数。
  2. 接口中抽象方法的返回值必须跟定义的方法的返回值一样。

这就是java8引入的新特性,一切为了简洁明了。

方法不是static怎么写?

理解了上面,这个就比较好懂了,贴一下代码,不做解释了

public class Program {
public static void main(String[] args) {
//创建一个Program对象
Program p = new Program();
//使用lambda的方法引用调用max方法,隶属者为对象
Comparator comparator = p::max;
int res = comparator.compare(1, 10);
System.out.println(res);
}
//没有被static修饰的方法
public int max(int a,int b){
return a > b ? a : b;
}
}


练习题

素材如下:

Person.java

class Person{
String name ;
int age;

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}


准备集合

public class Program {
public static void main(String[] args) {
List<Person> list = new ArrayList<>();
list.add(new Person("张三",18));
list.add(new Person("李四",30));
list.add(new Person("王五",16));
list.add(new Person("赵六",20));
list.add(new Person("田七",40));
list.add(new Person("马八",25));
printList(list);
}

//打印集合
public static void printList(List<Person> list){
for (Person person : list) {
System.out.println(person);
}
}
}


1. 将集合按照指定属性进行排序

按照person的年龄进行从低到高排序

普通的排序方式:

//使用正常的方式排序
list.sort(new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
});


问题:你能将普通的排序方式改造成lambda的方式吗?写出来后思考还有其他写法吗?

2. 将​​list.forEach​​方法改造成lambda方式

正常的list遍历:

list.forEach(new Consumer<Person>() {
@Override
public void accept(Person person) {
System.out.println(person.toString());
}
});


问题:如何改造成lambda的方式??改造之后修改练习1中的遍历代码

3.删除集合中指定元素

删除年龄大于35的人

正常的删除:

list.removeIf(new Predicate<Person>() {
@Override
public boolean test(Person person) {
return person.age > 35;
}
});


问题:如何改造成lambda的方式??

答案

  1. 排序(二选一)
list.sort((p1,p2) -> p1.getAge() - p2.getAge());
Collections.sort(list,(p1,p2) -> p1.getAge() - p2.getAge());


  1. 遍历
list.forEach(person -> System.out.println(person.toString()));


  1. 删除指定元素
list.removeIf(person -> person.age > 35);