1、Lambda表达式的使用
1.1、概念
Lambda 表达式的本质 : 函数式接口的对象,当一个函数式接口采用匿名内部类创建对象的时候,可以使用Lambda表达式
1.2、什么是函数接口?
函数式接口: 只有一个抽象方法的接口
//这个接口的作用是验证此为函数式接口,换句话说只要接口中只有一个抽象方法就是函数式接口,与加不加@FunctionalInterface没有关系
@FunctionalInterface
public interface MyInterface {
void sout(); //只能有一个抽象方法,有两个抽象方法就会报错,主要是为了避免产生歧义,有多个方法就不知道调哪个了
}
Java中提供了4大基本的函数式接口:
public interface Consumer<T> {
void accept(T t) //消费型接口,接收一个参数对象但是不返回
}
public interface Supplier<T>{
T get() //供给型接口,不接受参数但是返回一个对象
}
public interface Function<T,R>{
R apply(T t) //函数型接口 ,接收T类型的参数,进行操作,返回R类型的对象
}
public interface Predicate<T>{
boolean test(T t)//断定型接口,接收T类型的参数,判断其是否满足某种约束,返回一个boolean值
}
代码举例:
public static void main(String[] args) {
List<String> list = Arrays.asList("北京" ,"南京","上海","武汉","广州","深圳");
//未使用Lambda表达式时,才有匿名内部类创建函数式接口对象
list.forEach(new Consumer<String>() {
@Override
public void accept(String s) {
System.out.println(s);
}
});
//使用了Lambda表达式创建了Consumer这种函数式接口的对象,传入到方法中
list.forEach(s -> {
System.out.println(s);
});
}
其中foreach方法接收的是Consumer这种类型的函数型接口:
default void forEach(Consumer<? super T> var1)
语法中可省略内容:
- 参数列表:括号中的参数列表的数据类型,可以省略不写
- 参数列表:括号中的参数如果只有一个,那么类型和()都可以省略
- 一些代码:如果{}中的代码只有一行,无论是否有返回值,都可以省略
{}
、return
、;
2、Lambda表达式的原理
2.1、思路总结
- Lambda表达式是一个语法糖,在编译器会解语法糖,会生成
invokedynamic
字节码指令和一个私有的私有的Lambda方法 - invokedynamic会调用BootstrapMethods引导方法,即调用LambdaMetafactory.metafactory(),这个方法会通过ASM字节码技术,在运行时动态的写入一个匿名内部类字节码,该类实现了函数式接口(这里是Consumer)
- 在通过反射创建这个匿名内部类对象,通过该对象去调用编译期生成的Lambda方法
归根结底:Lambda的一系列操作就是生成了一个函数式接口的实现,通过这个实现去调用对应的Lambda方法
语法糖:指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。简而言之,语法糖让程序更加简洁,有更高的可读性。
解语法糖:前面提到过,语法糖的存在主要是方便开发人员使用。但其实,Java虚拟机并不支持这些语法糖。是Java编译器实现的,这些语法糖在编译阶段就会被还原成简单的基础语法结构,这个过程就是解语法糖。
2.2、源码分析
源代码如下:
public class LambdaTest {
public static void main(String[] args) {
List<String> list = Arrays.asList("C" , "h" , "a" ,"i" ,"m","e","k");
list.forEach(s -> {
System.out.print(s);
});
}
}
反编译如下:
public class LambdaTest {
public static void main(String[] stringArray) {
List<String> list = Arrays.asList("C", "h", "a", "i", "m", "e", "k");
//invokedynamic 指令会调用BootstrapMethods引导方法,即调用LambdaMetafactory.metafactory()
list.forEach((Consumer<String>)LambdaMetafactory.metafactory(
null, null, null,
(Ljava/lang/Object;)V,
lambda$main$0(java.lang.String ),
(Ljava/lang/String;)V)()
);
}
private static /* synthetic */ void lambda$main$0(String string) {
System.out.print(string);
}
}
使用jclasslib查看:
invokedynamic会调用BootstrapMethods引导方法,即调用LambdaMetafactory.metafactory():
ASM字节码技术,在运行时动态的写入一个匿名内部类字节码,该类实现了函数式接口(这里是Consumer),此时并没有创建该对象:
其中匿名内部类的类名为:
父接口为Consumer ,因为遍历使用的foreach要传入Consumer类型:
通过反射创建对象:
通过创建的匿名内部类对象调用Lambda表达式方法: