Lambda表达式是Java8的重要更新,也是被广大开发者寄予厚望的新特性。
Lambda表达式支持将代码块作为方法参数,允许用更加简洁的代码来创建只有一个抽象方法的接口的实例。
我们先来看一下使用Lambda表达式改写使用匿名内部类的示例:
public class Test{
public static void main(String[] args){
int[] lists = {1,2,3,4,5,6};
ProcessArray pa = new ProcessArray();
pa.process(lists,new Process(){
//匿名内部类,实现了Process接口,自定义了对整型数组的处理方式
public int method(int[] a){
int total = 0;
for(int item : a){
total += item;
}
return total;
}
});
}
}
class ProcessArray{
public void process(int[] lists,Process p){
//运用函数式接口,对整型数组lists进行处理
System.out.print(p.method(lists));
}
}
interface Process{
//必须实现一个处理整型数组的方法
int method(int[] a);
}
我们希望能够在处理整型数组时有一个通用的接口,方便我们自定义对整型数组的处理方式。下面是使用Lambda表达式对该示例的改写:
public class Test{
public static void main(String[] args){
int[] lists = {1,2,3,4,5,6};
ProcessArray pa = new ProcessArray();
pa.process(lists,(int[] a)->{
//Lambda表达式开始自定义
int total = 0;
for(int item : a){
total += item;
}
return total;
});
}
}
class ProcessArray{
public void process(int[] lists, Process p){
System.out.print(p.method(lists));
}
}
interface Process{
int method(int[] a);
}
这段代码和上一段代码的结果完全相同,但是省去了诸如new XXX(){}这样的繁琐代码,也不需要指出重写的方法名字、重写的方法的返回值类型——只要给出括号以及重写方法的形参列表即可。
从上面的示例可以总结得出Lambda表达式的三个组成部分:
- 形参列表。形参列表允许省略形参类型。如果形参列表只有一个参数,甚至可以连形参列表的圆括号都可以省略。如上面代码可以省略为a->。
- 箭头->。必须使用英文中的画线和大于符号组成。
- 代码块。如果代码块只包含一条代码,那么Lambda表达式允许省略代码块的花括号,这条语句结束则表达式结束。如果只有一条return语句,甚至可以省略return关键字。
Lambda表达式的类型也被称为“目标类型”,Lambda表达式的目标类型必须是“函数式接口”。函数式接口代表只含有一个抽象方法的接口,可以包含多个默认方法、类方法但只能声明一个抽象方法。
Java8专门为函数式接口提供了@FunctionalInterface注解。该注解可以放置在接口定义前。虽然对程序功能没有任何影响,但是它会告诉编译器执行更加严格的检查,检查该接口必须是函数式接口,否则报错。
由于Lambda表达式的结果就是被当成对象,所以程序中完全可以使用Lambda表达式进行赋值,示例如下:
public class Test{
public static void main(String[] args){
int[] lists = {1,2,3,4,5,6};
Runnable r = ()->{
System.out.print("AmosH's blog");
};
r.run();
//output AmosH's blog
}
}
从上面的代码可以看出,Lambda表达式实现的是匿名方法,因此只能实现特定函数式接口中的唯一方法。这意味着Lambda表达式有如下两个限制:
- Lambda的目标类型必须是明确的函数式接口
- Lambda表达式只能为函数式接口创建对象,且只能实现一个方法。
关于第一点,需要注意的是,同样的Lambda表达式的目标类型完全是可以变化的,唯一要求是Lambda表达式实现的匿名方法与目标类型(函数式接口)中的唯一的抽象方法必须有相同的形参列表。
综上所述,不难发现Lambda表达式的本质其实很简单,就是使用简洁的语法来创建函数式接口的实例,避免了匿名内部类的繁琐。
而Lambda表达式和匿名内部类的主要区别是:
- 匿名内部类可以为任意接口创建实例,而Lambda表达式只可以为函数式接口创建实例。
- 匿名内部类可以为抽象类甚至是普通类创建实例,Lambda表达式则不行。
- 匿名内部类实现的抽象方法可以调用接口中定义的默认方法,但是Lambda表达式的代码块不允许调用接口中定义的默认方法。
前面已经介绍过了,当Lambda表达式的代码块只有一条代码可以省略花括号。不仅如此,当代码块只有一条代码时,还可以在代码块中使用方法引用和构造器引用,让代码更加简洁。
- 引用类方法
不使用引用类方法之前的Lambda表达式程序示例:
public class Test{
public static void main(String[] args){
Convert c = (String from)->Integer.valueOf(from);
System.out.print(c.convert("123"));
}
}
interface Convert{
int convert(String from);
}
引用类方法会将函数式接口中的所有参数传入该类方法作为参数,调用示例:
类名::类方法
下面是使用了引用类方法的示例:
public class Test{
public static void main(String[] args){
Convert c = Integer::valueOf;
System.out.print(c.convert("123"));
}
}
interface Convert{
int convert(String from);
}
- 引用特定对象的实例方法
引用特定对象的实例方法会将被实现方法的全部参数传给该方法作为参数。
示例如下:
public class Test{
public static void main(String[] args){
Convert c = "AmosH's blog"::indexOf;
System.out.print(c.convert("blog"));
}
}
interface Convert{
int convert(String from);
}
- 引用构造器
引用构造器会将被实现方法的全部参数传给该构造器作为参数。
示例如下:
public class Test{
public static void main(String[] args){
NewOne no = Person :: new;
Person p = no.build("AmosH");
p.talk();
}
}
interface NewOne{
Person build(String target);
}
class Person{
private String name;
public Person(String n){
name = n;
}
public void talk(){
System.out.print("My name is"+name);
}
}
总结:
Lambda表达式是Java中为了让程序更加简洁而诞生的新特性。在实现函数式接口时较匿名内部类更加简洁更有优势,但同时也意味着它只能在函数式接口上发挥作用。
Lambda表达式的方法引用和构造器引用不仅可以让其更加简洁,还可以提高代码的可读性,方便日后的维护工作。