这篇文章是对自己学习的一个总结,学习资料是疯狂Java讲义第三版,李刚编,电子工业出版社出版。
Lambda表达式入门
匿名内部类可以现写一个类当做方法的参数,比较灵活,有关匿名内部类的知识可以看这篇文章。
比如下面的代码,ProcessArray类中有一个process方法,参数需要一个数组和一个Command接口的子类,其中Command接口有一个抽象的process方法。
public interface Command {
public void process(int[] target);
}
public class ProcessArray {
public void process(int[] target, Command command){
command.process(target);
}
}
public class Test {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] target = {3, -4, 5, 4};
int x = 2;
pa.process(target, new Command()
{
public void process(int[] target){
int sum = 0;
for(int temp: target){
System.out.println(x);
System.out.println(temp);
}
}
});
}
}
执行结果如下
这是匿名内部类的一个示例,现写的匿名内部类就可以简单地看成一个方法,可以直接调用函数内的变量。
Lambda表达式可以让上面的代码变得更加简单,用Lambda改写上面的代码
public class Test {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] array = {3, -4, 5, 4};
int x = 2;
pa.process(array, (int[] target)->{
int sum = 0;
for(int temp: target){
System.out.println(x);
System.out.println(temp);
}
});
}
}
可以看出,Lambda表达式就是用简单的写法代替匿名内部类。
省略掉new Command(), 省略掉方法名public void process,直接以形参(int[] target)代替上面的两个部分。这里注意(int[] target)不能和主函数内的变量重名,虽然他只是一个形参。
因为是以形参列表代替接口名和方法名,所以Lambda表达式只能适用于取代只有一个抽象方法的接口或者只有一个抽象方法的抽象类。
Lambda表达式还可以再简化一些,下面是一些简化的规则。
- 可以直接省略方法中形参的类型。
- 如果方法的形参只有一个,那可以省略圆括号。比如下面这个
pa.process(array, target ->{
int sum = 0;
for(int temp: target){
System.out.println(x);
System.out.println(temp);
}
});
- 如果方法的代码块只有一个语句,那么可以省略花括号,和语句后面的分号,比如下面
pa.process(array, (int[] target) -> target = null);
- 如果抽象方法有返回值,并且方法体中只有一条return 语句,那么可以省略return,比如下面的代码
pa.process(array, (a, b) -> a + b);
因为Lambda表达式都是抽象方法只有一个的接口,所以,很多东西都是确定的,不用显示写出来。
Lambda表达式与函数式接口
所谓的函数式接口,就是只有一个抽象方法的接口或抽象类。抽象类中可以有多个非抽象的方法(不用变量,接口也是,只关注方法),
但抽象类或接口的抽象方法只有一个,这样的抽象类或接口才是函数式接口。
查看Java8的文档,会发现很多函数式接口,比如Runnable,ActionListener等。
Java 8专门为函数式接口提供了@FunctionalInterface注解,这个注解不会做什么,作用和@Override类似,只是让编译器检查这个类或接口是不是函数式接口,不是的话通不过编译。
所以能被Lambda表达式替代的都是函数式接口。
方法引用和构造器引用
Lambda表达式还可以写得更简略,当Lambda代码块只有一条代码时,并且这条语句还是引用其它的方法或者构造器,那就可以用方法引用和构造器引用来让代码更简略。
Lambda表达式支持四种引用方式:引用类方法,引用指定对象的实例方法,引用某类对象的实例方法,引用构造器。
-
引用类方法
比如定义一个函数接口,抽象方法用于将字符串转换成数字。
@FunctionalInterface
interface Converter{
public Integer convert(String string);
}
主函数中可以调用Integer.valueof(string)这个方法,Lambda表达式就可以这样写。
Converter converter = string -> Integer.valueOf(string);
上面的代码可以简写成
Converter converter = Integer::valueOf;
这就是类方法引用。
如果函数接口的方法中只有一条语句,并且该方法还是调用一个类方法,那么这句代码可以简写成 类名::类方法名, 其中类方法名不用写后面的括号和形参,这样的写法是将函数式接口中的参数全部传给类方法中的参数。比如上面的 convert中的全部参数都传给了Integer.valueOf()方法。
-
引用特定对象的实例方法
还是使用上面的例子,假如我们有一个类A,A中有一个实例方法valueOf()和Integer.valueOf的效果完全一样,那我们的Lambda表达式可以这样写
Converter converter = string -> {
A a = new A();
return a.valueOf(string);
}
使用引用简写就可以写成
A a = new A();
Converter converter = a::valueOf;
所以简写规则就是 类实例名::实例方法名,同样的,实例方法名不需要写括号和形参,默认将函数式接口中的参数全部传给实例方法中的参数。
-
引用某类对象的实例方法
换一个例子,声明一个函数时接口,接口的方法是用来执行字符串的裁剪功能。
public interface Command {
public String subString(String a, int b, int c);
}
输入字符串a,起始坐标b和终止坐标c,返回字符串的[b,c)的子串。
那么用Lambda表达式就可以写成
Command command = (a, b, c) -> a.substring(b, c);
这个可以更简略,可以引用类对象的实例方法,可以写成
Command command = String::substring;
它的语法就是 抽象方法的第一个参数的类名::第一个参数的类的实例方法名,它的应用场景是,当抽象方法有多个参数,并且抽象方法的实现部分只有一句代码,并且这句代码是以第一个参数作为调用者,后面的参数都是该调用者调用的方法的参数。
以上面的例子为例,抽象方法subString(String a, int b, int c)有多个参数,该方法的实现类只有a.subString(b, c)着一行语句,
这一行语句是以第一个参数a为调用者,剩余的b和c作为a调用的subString()方法的参数,a.subString(b, c)。
这样的情况,Lambda表达式就简写成第一个参数的类名 String,和这个类调用的方法subString组合而成,String::substring。
-
引用构造器
当函数接口的实现方法只有一行语句,
并且该行语句是一个构造器,
并且实现方法的所有参数都是构造器的所需要的参数(顺序必须相同),
那么Lambda表达式可以简写成 拥有这个构造器的类的类名::new 。
比如我们定义一个函数接口
public interface Test{
public JFrame newFrame(String a);
}
这个接口的方式是为了得到一个标题为a的JFrame对象。所以Lambda表达式可以这样写。
Test test = a -> new JFrame(a);
那么这样的情况,
实现方法的主体语句只要一行,
主体语句 new JFrame(a)是一个构造器,
构造器的参数和接口方法的参数相同,都是String a,
所以该Lambda表达式可以简写成 拥有该构造器的类名 JFrame,加上new,即为
Test test = JFrame::new;