Java 8 的 Lambda 表达式
Lambda 表达式是 Java 8 的重要更新,也是一个被广大开发者期待已久的新特性。 Lambda 表达式支持将代码块作为方法参数, Lambda 表达式允许使用更简洁的代码来创建只有一个抽象方法的接口的实例。
这种接口被称为函数式接口。
Lambda 表达式完全可以用于简化创建匿名内部类对象。
public class CommandTest2
{
public static void main(String[] args)
{
ProcessArray pa = new ProcessArray();
int[] array = {3, -4, 6, 4};
// 处理数组,具体处理行为取决于匿名内部类
pa.process(array , (int[] target)->{
int sum = 0;
for (int tmp : target )
{
sum += tmp;
}
System.out.println("数组元素的总和是:" + sum);
});
}
}
--------------------------------------------------------------------
不需要 new XXX(){} 这种繁琐的代码,不需要指出重新方法的方法名,也不需要给出重写方法的返回值类型:只要给出重写的方法括号及括号里的形参列表即可。
当使用 Lambda 表达式代替匿名内部类创建对象时, Lambda 表达式的代码块将会代替实现抽象方法的方法体, Lambda 表达式就相当于一个匿名方法。
Lambda 表达式的主要作用就是代替匿名内部类的繁琐语法,它由三部分组成:
1. 形参列表: 形参列表允许省略形参类型。如果形参列表中只有一个参数,甚至连形参列表的圆括号也可以省略。
2. 箭头 -> : 必须通过英文中划线和大于符号组成。
3. 代码块 :如果代码块只包含一条语句, Lambda 表达式允许省略代码块的花括号,那么这条语句就不要用花括号表示语句结束。 Lambda 表达式只有一条 return 语句,
甚至可以省略 return 关键字。 Lambda 表达式需要返回值, 而它的代码块中仅有一条省略了 return 的语句, Lambda 表达式会自动返回这条语句的值。
interface Eatable
{
void taste();
}
interface Flyable
{
void fly(String weather);
}
interface Addable
{
int add(int a , int b);
}
public class LambdaQs
{
// 调用该方法需要Eatable对象
public void eat(Eatable e)
{
System.out.println(e);
e.taste();
}
// 调用该方法需要Flyable对象
public void drive(Flyable f)
{
System.out.println("我正在驾驶:" + f);
f.fly("【碧空如洗的晴日】");
}
// 调用该方法需要Addable对象
public void test(Addable add)
{
System.out.println("5与3的和为:" + add.add(5, 3));
}
public static void main(String[] args)
{
LambdaQs lq = new LambdaQs();
// Lambda表达式的代码块只有一条语句,可以省略花括号。
lq.eat(()-> System.out.println("苹果的味道不错!"));
// Lambda表达式的形参列表只有一个形参,省略圆括号
lq.drive(weather ->
{
System.out.println("今天天气是:" + weather);
System.out.println("直升机飞行平稳");
});
// Lambda表达式的代码块只有一条语句,省略花括号
// 代码块中只有一条语句,即使该表达式需要返回值,也可以省略return关键字。
lq.test((a , b)->a + b);
}
}
*
*
*
2. Lambda 表达式与函数式接口
-------------------------------------------------------------------------------------------
Lambda 表达式的类型,也被称为 “目标类型 (target type)”, Lambda 表达式的目标类型必须是“函数式接口 ( functional interface )”。
函数式接口可以包含多个默认方法、类方法,但只能声明一个抽象方法。
如果采用匿名内部类语法来创建函数式接口的实例,则只需要实现一个抽象方法,在这种情况下即可采用 Lambda 表达式来创建对象,该表达式创建出来的对象的目标类型就是这个函数式接口。
@FunctionalInterface 注解:该接口通常放在接口定义前面,该注解对程序功能没有任何作用,它用于告诉编译器执行更严格的检查-------检查该接口必须是函数式接口,否则编译器报错。
由于 Lambda 表达式的结果就是被当成对象,因此程序中完全可以使用 Lambda 表达式进行赋值。
@FunctionalInterface
interface FkTest
{
void run();
}
public class LambdaTest
{
public static void main(String[] args)
{
// Runnable接口中只包含一个无参数的方法
// Lambda表达式代表的匿名方法实现了Runnable接口中唯一的、无参数的方法
// 因此下面的Lambda表达式创建了一个Runnable对象
Runnable r = () -> {
for(int i = 0 ; i < 100 ; i ++)
{
System.out.println();
}
};
// // 下面代码报错: 不兼容的类型: Object不是函数接口
// Object obj = () -> {
// for(int i = 0 ; i < 100 ; i ++)
// {
// System.out.println();
// }
// };
Object obj1 = (Runnable)() -> {
for(int i = 0 ; i < 100 ; i ++)
{
System.out.println();
}
};
// 同样的Lambda表达式可以被当成不同的目标类型,唯一的要求是:
// Lambda表达式的形参列表与函数式接口中唯一的抽象方法的形参列表相同
Object obj2 = (FkTest)() -> {
for(int i = 0 ; i < 100 ; i ++)
{
System.out.println();
}
};
}
}
------------------------------------------------------------------------------------------
Lambda 表达式只能实现特定函数式接口中的唯一方法,这意味着 Lambda 表达式有如下两个限制:
1. Lambda 表达式的目标类型必须是明确的函数式接口
2. Lambda 表达式只能为函数式接口创建对象。
为了保证 Lambda 表达式的目标类型是一个明确的函数式接口,可以有如下三种常见方式:
1. 将 Lambda 表达式赋值给函数式接口类型的变量。
2. 将 Lambda 表达式作为函数式接口类型的参数传给某个方法
3. 使用函数式接口对 Lambda 表达式进行强制类型转换。
Java 8 在 java.util.function 包下预定义了大量函数式接口,典型地包含如下4类接口:
1. XxxFunction: 这类接口通常包含一个 apply() 抽象方法,该方法对参数进行处理、转换。 apply() 方法的处理逻辑由 Lambda 表达式来实现,然后返回一个新的值。该函数式接口通常用于对指定数据进行转换处理。
2. XxxConsumer: 这类接口通常包含一个 accept() 抽象方法, 该方法也负责对参数进行处理,只是该方法不会返回处理结果。
3. XxxPredicate: 这类接口中通常包含一个 test() 抽象方法,该方法通常用来对参数进行某种判断。 test() 方法的判断逻辑由 Lambda 表达式实现, 然后返回一个 boolean 值。 该接口常用于判读参数是否满足特定条件,经常用于进行筛选数据。
4. XxxSupplier: 这类接口通常包含一个 getAsXxx() 抽象方法,该方法不需要输入参数,该方法会按某种逻辑算法返回一个数据。 getAsXxx() 方法的逻辑算法由 Lambda 表达式实现。
*
*
*
3. 方法引用与构造器引用
-----------------------------------------------------------------------------------------
如果 Lambda 表达式的代码块只有一条代码,可以在代码块中使用方法引用和构造器引用。
方法引用和构造器引用可以让 Lambda 表达式的代码块更加简洁。方法引用和构造器引用都需要使用两个英文冒号。
|-------------------|-------------------|--------------------------------------------------------|---------------------------------------|
| 种类 | 示例 | 说明 | 对应的 Lambda 表达式 |
|-------------------|-------------------|--------------------------------------------------------|---------------------------------------|
| 引用类方法 | 类名::类方法 | 函数式接口中被实现方法的全部参数传给该类方法作为参数 | (a,b,...)->类名.类方法(a,b,...) |
|-------------------|-------------------|--------------------------------------------------------|----------------------------------------
| 引用特定对象 |特定对象::实例方法 | 函数式接口中被实现方法的全部参数传给该方法作为参数 | (a,b,...)->特定对象.实例方法(a,b,...) |
| 的实例方法 | | | |
|-------------------|-------------------|--------------------------------------------------------|---------------------------------------|
| 引用某类对象的 | 类名::实例方法 | 函数式接口中被实现方法的第一个参数作为调用者, | (a,b,...)->a.实例方法(b,...) |
| 实例方法 | | 后面的参数全部参数传给该方法作为参数。 | |
|-------------------|-------------------|--------------------------------------------------------|---------------------------------------|
| 引用构造器 | 类名::new | 函数式接口中被实现方法的全部参数传给该构造器作为参数 | (a,b,...)->new 类名(a,b,...) |
|----------------------------------------------------------------------------------------------------------------------------------------|
*
*
*
4 Lambda 表达式与匿名内部类的联系和区别
------------------------------------------------------------------------------------------
Lambda 表达式是匿名内部类的一种简化,因此它可以部分取代匿名内部类的作用, Lambda 表达式与匿名内部类存在如下相同点:
1. Lambda 表达式与匿名内部类一样,都可以直接访问 "effectively final" 的局部变量,以及外部类的成员变量,包括实例变量和类变量。
2. Lambda 表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法。
Lambda 表达式与匿名内部类主要存在如下区别:
1. 匿名内部类可以为任意接口创建实例---不管接口包含多少个抽象方法,只要匿名内部类实现所有抽象方法即可;但 Lambda 表达式只能为函数式接口创建实例。
2. 匿名内部类可以为抽象类甚至普通类创建实例;但 Lambda 表达式只能为函数式接口创建实例。
3. 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但 Lambda 表达式的代码块不允许调用接口中定义的默认方法。
*
*
*
5. 使用 Lambda 表达式调用 Arrays 的类方法
------------------------------------------------------------------------------------------
Arrays 类的有些方法需要 Comparator XxxOperator XxxFunction 等接口实例,这些都是函数式接口,因此可以使用 Lambda 表达式调用 Arrays 的方法。