Lambda表达式的主要作用就是代替匿名内部类的繁琐语法。它由三部分组成。
下面程序示范了Lambda表达式的几种简化写法。
interface Eatable{
void taste();
}
interface Flyable{
void fly(String weather);
}
interface Addable{
int add(int a, int b);
}
public class LambdaQs {
public void eat(Eatable e) {
System.out.println(e);
e.taste();
}
public void drive(Flyable f) {
System.out.println("我正在驾驶:" + f);
f.fly("【碧空如洗的晴天】");
}
public void test(Addable add) {
System.out.println("5与3的和:" + add.add(3, 5));
}
public static void main(String[] args) {
LambdaQs lq = new LambdaQs();
lq.eat(()->System.out.println("苹果的味道不错"));
lq.drive(weather ->{
System.out.println("今天的天气是:" + weather);
System.out.println("直升机飞行很平稳");
});
lq.test((a, b)-> a+b);
}
}
Lambda表达式的类型,也被称为“目标类型”,Lambda表达式的目标类型必须是“函数式接口”。函数式接口代表只包含一个抽象方法的接口。函数式接口可以包含多个默认方法。类方法,但只能声明一个抽象方法。
如果采用匿名内部类语法来创建函数式接口的实例,则需要实现一个抽象方法,在这种情况下即可采用Lambda表达式来创建对象,该表达式创建出来的对象的目标类型就是这个函数式接口。
由于Lambda表达式的结果就是被当成对象,因此程序中完全可以使用Lambda表达式进行赋值,例如下面代码
//Runnable 接口中只包含一个无参数的方法
//Lambda 表达式代表的匿名方法实现了Runnable接口中唯一的、无参数的方法
//下面的Lambda 表达式创建了一个Runnable对象
Runnable r = ()->{
for (int i = 0; i < 100 ; i++){
System.out.println();
}
}
Lambda 表达式有如下两个限制
为了保证Lambda表达式赋值的目标类型是一个明确的函数式接口,可以有如下三种方式。
//这段代码是错的 Object不是函数接口变量
Object obj = ()->{
for(int i = 0; i < 10 ; i++){
System.out.println();
}
};
//将对Lambda执行强制类型转换。
Object obj = (Runnable)()->{
for(int i = 0; i < 10 ; i++){
System.out.println();
}
};
同样的Lambda表达式的目标类型完全可能是变化的——唯一的要求是,Lambda表达式实现的匿名方法与目标类型中惟一的抽象方法有相同的形参列表。
定义如下接口
@FuntionalInterface
interface FkTest{
void run();
}
上面的函数式接口中仅定义了一个不带参数的方法,因此前面强制转型为Runnable的Lambda表达式也可以强制转为类型FkTest类型——因为FkTest接口中唯一的抽象方法是不带参数的,而该Lambda表达式也是不带参数的。下面代码也是正确的。
Object obj = (FkTest)()->{
for(int i = 0; i < 10 ; i++){
System.out.println();
}
};
Java 8 在java.uit.function包下预定义了大量函数式接口,典型地包含如下4类接口。
方法引用和构造器引用可以让Lambda表达式的代码块更加简洁。方法引用和构造器引用都需要使用两个英文冒号。
Lambda表达式支持如下表所示的几种引用方式。
种类 | 示例 | 说明 | 对应的Lambda表达式 |
引用类方法 |
类名::类方法 | 函数式接口中实现方法的全部参数传给该方法作为参数 | (a,b,...)->类名.类方法(a,b,...) |
引用特定对象实例方法 | 特定对象::实例方法 | 函数式接口中被实现方法的全部参数传给该方法作为参数 | (a,b,...)->特定对象.实例方法(a,b,...) |
引用某类对象实例方法 | 类名::实例方法 | 函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数 | (a,b,...)-> a.实例方法(b,...) |
引用构造器 | 类名::new | 函数式接口中被实现方法的全部参数传给该构造器作为参数 | (a,b,...)->new 类名(a,b,...) |
引用类方法。定义了如下函数式接口。
@functionalInterface
interface Converter
{
Integer convert (String from);
}
该函数式接口中包含一个convert()抽象方法,该方法负责将String参数转换为Integer。下面代码使用Lambda表达式来创建一个Converter对象
Converter converter1 = from -> Integer.valueOf(from);
接下来程序就可以调用converter1对象的convert()方法将字符串转换为整数了,代码如下
Integer val = converter1.convert("99");
System.out.println(val);
上面代码可以用如下方法引用进行替换
//方法引用代替Lambda表示式:引用类方法
// 函数式接口中被实现方法的全部参数传给该类方法做诶参数
Converter converter1 = Integer::valueOf;
引用特定对象的方法。先使用Lambda表达式来创建一个Converter对象
// 使用Lambda表达式创建Converter对象
Converter converter2 = from -> "fkit.org".indexOf(from);
程序可以调用converter1对象的convert()方法将字符串转换为整数了,例如下面代码
Integer value = converter2.convert("it");
System.out.println(value);
上面Lambda表达式的代码块只有一行调用“fkit.org”对象的indexOf()实例方的代码,因此可以使用如下方法引用进行替换。
Converter converter2 = "fkit.org"::indexOf;
引用某类对象的实例方法
@functionalInterface
interface MyTest{
String test(String a, int b, int c);
}
下面代码使用Lambda表达式来创建一个MyTest对象
MyTest mt = (a, b, c)-> a.substring(b,c);
接下来程序就可以调用mt对象的test()方法了,例如下面代码
String str = mt.test("Java I Love you", 2, 9);
System,out.println(str);
上面Lambda表达式的代码块只有一行a.substring(b,c);因此可以使用如下方法引用进行替换
MyTest mt = String::substring;
构造器的引用
@FunctionalInterface
interface YourTest{
JFrame win(String title);
}
下面代码使用Lambda表达式来创建一个YourTest对象
YourTest yt = (String a) -> new JFrame(a);
接下来程序就可以调用yt对象的win()方法了,如下代码
JFrame jf = yt.win("我的窗口");
System.out.println(jf);
上面Lambda表达式的代码只有一行new JFrame(a)因此可以使用如下构造器引用进行替换
YourTest yt = JFrame :: new;
下面程序示范了Lambda表达式与匿名内部类的相似之处
@FunctionalInterface
interface Displayable{
void display();
default int add(int a, int b) {
return a + b;
}
}
public class LambdaAndInner {
private int age = 12;
private static String name = "教育中心";
public void test() {
String book = "Java";
Displayable dis = ()->{
System.out.println("book 局部变量为:" + book);
System.out.println("外部类的age实例变量:" + age);
System.out.println("外部类的name类变量:" + name);
};
dis.display();
System.out.println(dis.add(3, 5));
}
public static void main(String[] args) {
LambdaAndInner lambda = new LambdaAndInner ();
lambda.test();
}
}
Lambda表达式与匿名内部类的区别
对于Lambda表达式的代码块不允许调用接口中定义的默认方法的限制,可以尝试对上面的LambdaAndInner.java程序修改,在Lambda的代码块中增加一行:
//尝试调用接口中的默认方法,编译器会报错
System.out.println(add(3,5));
Arrays类的有些方法需要Comparator、XXXOperator、XxxFunction等接口的实例,这些接口都是函数式接口,因此可以使用Lambda表达式来调用Arrays的方法。程序如下
import java.util.Arrays;
public class LambdaArrays {
public static void main(String[] args) {
String [] arr1 = new String [] {"java" , "fkava" , "fkit" , "ios" , "android"};
Arrays.parallelSort(arr1, (o1,o2)-> o1.length() - o2.length());
System.out.println(Arrays.toString(arr1));
int [] arr2 = new int [] {3, -4, 25, 16, 30, 18};
Arrays.parallelPrefix(arr2, (left, right) -> left * right);
System.out.println(Arrays.toString(arr2));
long[] arr3 = new long[5];
Arrays.parallelSetAll(arr3, operand -> operand *5);
System.out.println(Arrays.toString(arr3));
}
}