1 概述
Lambda表达式是一个可传递的代码块,可以再以后执行一次或多次
函数式接口:只有一个抽象方法的接口
函数式接口就是只包含一个抽象方法的接口,函数式接口可以包含多个默认方法、类方法、但只能声明一个抽象方法,Lambda表达式的目标类型必须是函数式接口。函数式接口和Lambda表达式是兼容的,Lambda表达式可以转换为接口。JAVA在java.util.function
包中定义了很多非常通用的函数式接口
- Lambda表达式的重点是延迟执行,常用于以下场景:
- 在一个单独的线程中运行代码
- 多次运行代码
- 在算法的适当位置运行代码
- 发生某种情况时执行代码
- 只有在必要时才运行代码
2 Lambda表达式
说明:Lambda表达式是Java 8的重要更新,Lambda表达式支持将代码块作为方法参数,Lambda表达式允许使用更简洁的代码创建只有一个抽象方法的接口(函数式接口)的实例。
2.1 Lambda表达式入门
Lambda表达式用于简化创建匿名内部类对象,不需要new Xxx()
,不需要指出重写的方法名字,不需要给出重写方法的返回值类型,只需要给出重写的方法括号以及括号里的形参列表即可,此时Lambda表达式的主要功能是代替实现抽象方法的主体,Lambda表达式就相当于一个匿名方法,Lambda表达式的主要作用就是代替匿名内部类的繁琐语法。
- 组成:参数列表 + 函数主体 + 返回类型
- 形式:参数列表 + 箭头 + Lambda主体
- 基本语法:(parameters) -> expression或(parameters) ->{ expression;}
- Lambda表达式的三部分组成:
- 形参列表:可省略形参类型;只有一个参数的时候,可省略圆括号
- 箭头(->)
- 代码块:{},只有一条语句,可以省略花括号,Lambda代码块只有一条
return
语句,甚至可以省略return
关键字。Lambda表达式需要返回值,而他的代码块中仅有一条省略了return
的语句,Lambda表达式会自动返回这条语句的值
- 注意:
- 即使Lambda表达式没有参数,任然要提供空括号,就像无参数方法一样
- 如果可以推导出一个Lambda表达式的参数类型,则可以忽略其类型
- 如果方法只有一个参数,而且这个参数的类型可以推到得出,那么甚至还可以省略小括号
- 无须指定Lambda表达式的返回类型,Lambda表达式的返回类型重视会由上下文推到得出
public class CommandTest {
public static void main(String[] args) {
ProcessArray pa = new ProcessArray();
int[] array = {3, -4, 6, 4};
// 匿名内部类
pa.process(array , new Command() {
public void process(int[] target) {
int sum = 0;
for (int tmp : target ) {
sum += tmp;
}
System.out.println("数组元素的总和是:" + sum);
}
});
// Lambda表达式
pa.process(array , (int[] target)-> {
int sum = 0;
for (int tmp : target ) {
sum += tmp;
}
System.out.println("数组元素的总和是:" + sum);
});
}
}
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();
// 只有一条语句,可以省略花括号。
lq.eat(()-> System.out.println("苹果的味道不错!"));
// 只有一个形参,省略圆括号
lq.drive(weather -> {
System.out.println("今天天气是:" + weather);
System.out.println("直升机飞行平稳");
});
// 只有一个return,省略return关键字。
lq.test((a , b)->a + b);
}
}
2.2 Lambda表达式与函数式接口
- Lambda表达式实现的是匿名方法——因此它只能实现特定函数式接口中的唯一方法,意味着有如下限制:
- Lambda表达式的目标类型必须是明确的函数式接口
- Lambda表达式只能为函数式接口创建对象。
- Lambda表达式只能实现一个方法,因此它只能为只有一个抽象方法的接口(函数式接口)创建对象
- Lambda表达式目标类型的三种常见方式:
- 将Lambda表达式赋值给函数式接口类型的变量
- 将Lambda表达式作为函数式接口类型的参数传给某个方法
- 使用函数式接口对Lambda表达式进行强制类型转换
函数式接口 | 参数类型 | 返回类型 | 抽象方法名 | 描述 | 其他方法 |
---|---|---|---|---|---|
Runnable | 无 | void | run | 作为无参数或返回值的动作运行 | |
Supplier<T> | 无 | T | get | 提供一个T类型的值 | |
Consumer<T> | T | void | accept | 处理一个T类型的值 | addThen |
BiConsumer<T,U> | T,U | void | accept | 处理T和U类型的值 | addThen |
Function<T,R> | T | R | apply | 有一个T类型参数的函数 | compose,addThen,identify |
BiFunction<T,U,R> | T,U | R | apply | 有T和U类型参数的函数 | addThen |
UnaryOperator<T> | T | T | apply | 类型T上的一元操作符 | compose,addThen,identify |
BinaryOperator<T> | T,T | T | apply | 类型T上的二元操作符 | addThen,maxBy,minBy |
Predicate<T> | T | boolean | test | 布尔值函数 | and,or,negate,isEqual |
BiPredicate<T,U> | T,U | boolean | test | 有两个参数的布尔值函数 | and,or,negate |
- 注意: Lambda表达式的目标类型完全可能是变化的——唯一的要求是,Lambda表达式实现的匿名方法与目标类型中唯一的抽象方法有相同的形参列表
3 方法引用与构造器引用
- 如果Lambda表达式中只有一条代码的话,还可以在代码块中使用方法引用和构造器引用
种类 | 示例 | 说明 | Lambda表达式 |
---|---|---|---|
引用类方法 | 类名::类方法 | 函数式接口中被实现方法的全部参数传给该方法作为参数 | (a,b,…)->类名.类方法(a,b,…) |
引用特定对象的实例方法 | 特定对象::实例方法 | 函数式接口中被实现方法的全部参数传给该方法作为参数 | (a,b,…)->特定对象.实例方法(a,b,…) |
引用某类对象的实例方法 | 类名::实例方法 | 函数式接口中被实现方法的第一个参数作为调用者,后面的参数全部传给该方法作为参数 | (a,b,…)->a.实例方法(a,b,…) |
引用构造器 | 类名::new | 函数式接口中被实现方法的全部参数传给该构造器作为参数 | (a,b,…)->new 类名(a,b,…) |
3.1 引用类方法
// 下面代码使用Lambda表达式创建Converter对象
Converter converter1 = from -> Integer.valueOf(from);
// 方法引用代替Lambda表达式:引用类方法。
// 函数式接口中被实现方法的全部参数传给该类方法作为参数。
Converter converter1 = Integer::valueOf;
Integer val = converter1.convert("99");
System.out.println(val); // 输出整数99
3.2 引用特定对象的实例方法
// 下面代码使用Lambda表达式创建Converter对象
Converter converter2 = from -> "fkit.org".indexOf(from);
// 方法引用代替Lambda表达式:引用特定对象的实例方法。
// 函数式接口中被实现方法的全部参数传给该方法作为参数。
Converter converter2 = "fkit.org"::indexOf;
Integer value = converter2.convert("it");
System.out.println(value); // 输出2
3.3 引用某类对象的实例方法
// 下面代码使用Lambda表达式创建MyTest对象
MyTest mt = (a , b , c) -> a.substring(b , c);
// 方法引用代替Lambda表达式:引用某类对象的实例方法。
// 函数式接口中被实现方法的第一个参数作为调用者,
// 后面的参数全部传给该方法作为参数。
MyTest mt = String::substring;
String str = mt.test("Java I Love you" , 2 , 9);
System.out.println(str); // 输出:va I Lo
3.4 引用构造器
// 下面代码使用Lambda表达式创建YourTest对象
YourTest yt = (String a) -> new JFrame(a);
// 构造器引用代替Lambda表达式。
// 函数式接口中被实现方法的全部参数传给该构造器作为参数。
YourTest yt = JFrame::new;
JFrame jf = yt.win("我的窗口");
System.out.println(jf);
附:完全代码
@FunctionalInterface
interface Converter{
Integer convert(String from);
}
@FunctionalInterface
interface MyTest {
String test(String a , int b , int c);
}
@FunctionalInterface
interface YourTest {
JFrame win(String title);
}
public class MethodRefer {
public static void main(String[] args) {
// 下面代码使用Lambda表达式创建Converter对象
Converter converter1 = from -> Integer.valueOf(from);
// 方法引用代替Lambda表达式:引用类方法。
// 函数式接口中被实现方法的全部参数传给该类方法作为参数。
Converter converter1 = Integer::valueOf;
Integer val = converter1.convert("99");
System.out.println(val); // 输出整数99
// 下面代码使用Lambda表达式创建Converter对象
Converter converter2 = from -> "fkit.org".indexOf(from);
// 方法引用代替Lambda表达式:引用特定对象的实例方法。
// 函数式接口中被实现方法的全部参数传给该方法作为参数。
Converter converter2 = "fkit.org"::indexOf;
Integer value = converter2.convert("it");
System.out.println(value); // 输出2
// 下面代码使用Lambda表达式创建MyTest对象
MyTest mt = (a , b , c) -> a.substring(b , c);
// 方法引用代替Lambda表达式:引用某类对象的实例方法。
// 函数式接口中被实现方法的第一个参数作为调用者,
// 后面的参数全部传给该方法作为参数。
MyTest mt = String::substring;
String str = mt.test("Java I Love you" , 2 , 9);
System.out.println(str); // 输出:va I Lo
// 下面代码使用Lambda表达式创建YourTest对象
YourTest yt = (String a) -> new JFrame(a);
// 构造器引用代替Lambda表达式。
// 函数式接口中被实现方法的全部参数传给该构造器作为参数。
YourTest yt = JFrame::new;
JFrame jf = yt.win("我的窗口");
System.out.println(jf);
}
}
4 Lambda表达式于匿名内部类的联系和区别
- 相同点:
- Lambda表达式与匿名内部类一样,都可以直接访问“effectively final”的局部变量,以及外部类的成员变量(包括实例变量和类变量)
- Lambda表达式创建的对象与匿名内部类生成的对象一样,都可以直接调用从接口中继承的默认方法。
- 不同点:
- 匿名内部类可以为任意接口创建实例——不管接口包含多少个抽象方法,只要匿名内部类实现所有的抽象方法即可;但是Lambda表达式只能为函数式接口创建实例(只有一个抽象方法的接口);
- 匿名内部类可以为抽象类甚至普通类创建实例;但是Lambda表达式只能为函数式接口创建实例;
- 匿名内部类实现的抽象方法的方法体允许调用接口中定义的默认方法;但Lambda表达式的代码块不允许调用接口中定义的默认方法。