文章目录
一、Lambda表达式使用
1、Lambda表达式介绍
Lambda表达式是Java8中非常重要的一个新特性,其基于函数式编程的思想,支持将代码作为方法参数进行使用。可以
把Lambda表达式理解为通过一种更加简洁的方式表示可传递的匿名函数。
它本身没有名称,而且不像方法那样属于某一个类,但是可以有参数列表、代码体、返回值。使用了Lambda表达式之后就不需要再去编写匿名类了。
2、Lambda使用规范
(1)Lambda基础格式
(参数列表) -> {方法体
}
参数列表:即匿名方法的形参
-> :Lambda运算符
方法体:用于执行业务逻辑。可以是单一语句,也可以是语句块。如果是单一语句,可以省略花括号。当需要返回值,如果方法体中只有一条语句,可以省略return,会自动根据结果进行返回。
// 1)没有参数的Lambda表达式
()->new Student();// 2)只有一个参数的Lambda表达式
x -> {System.out.println(x);return x;
}// 3)有多个参数的Lambda表达式
(int x,int y) ->{System.out.println(x);System.out.println(x);return x+y;
}
// 上述可以进行简写,因为在Lambda中,参数列表中参数的数据类型可以交给JVM根据上下文进行推断。所以可以不用定义类型。
(x,y) ->{System.out.println(x);System.out.println(y);return x+y;
}// 4)一个参数和仅一条语句的Lambda表达式
x -> 3+x;// 5)多个参数和仅一条语句的Lambda表达式
(x,y) -> x+y;
3、Lambda表达式与传统方式比对
(1)遍历集合
public static void main(String[] args) {String[] language = {"c", "c++","c#","java","python","go","hive","php"};List<String> languageList = Arrays.asList(language);//旧的循环方式for (String s : languageList) {System.out.println(s+",");}//lambda循环languageList.forEach(s-> System.out.println(s+","));
}
(2)使用Lambda替换匿名内部类使用
//匿名内部类
Runnable runnable = new Runnable() {@Overridepublic void run() {System.out.println("Hello world !");}
};//使用Lambda
Runnable runnable1 = ()-> System.out.println("hello world");
(3)实现Lambda实现集合排序
public static void main(String[] args) {String[] language = {"c", "c++","c#","java","python","go","hive","php"};//旧的循环比较方式Arrays.sort(language,new Comparator<String>(){@Overridepublic int compare(String o1, String o2) {return (o1.compareTo(o2));}});//lambda循环比较Arrays.sort(language,(o1,o2)-> (o1.compareTo(o2)));
}
二、Lambda表达式底层原理解析
1、反编译lambda
定义一个使用Lambda表达式的方法:
public class SourceDemo {public static void demo(){String[] language = {"c", "c++","c#","java","python","go","hive","php"};List<String> list = Arrays.asList(language);list.forEach(s-> System.out.println(s));}public static void main(String[] args) {SourceDemo.demo();}
}
将当前.java文件编译生成.class文件,执行命令后,会在当前文件夹生成对应的.class文件
javac SourceDemo.java
将.class文件进行反编译,查看文件内容:
javap -p SourceDemo.class
生成内容如下:
Compiled from "SourceDemo.java"
public class com.itheima.lambda.SourceDemo {public com.itheima.lambda.SourceDemo();public static void demo();public static void main(java.lang.String[]);private static void lambda$demo$0(java.lang.String);
}
此时可以发现,代码中执行Lambda表达式的部分生成了一个静态私有函数
。这个静态私有函数的函数干就是Lambda表达式里面的内容。
2、静态私有函数生成过程
那么对于这个静态私有函数,在JDK8内部是如何实现调用的呢?可以查看LambdaMetafactory类,该类下有一个metafactory方法,lambda表达式每一次在执行的时候都会进入到这个方法中,并且为lambda表达式创建一个内部类。
// java.lang.invoke.LambdaMetafactory#metafactory
public static CallSite metafactory(MethodHandles.Lookup caller,String invokedName,MethodType invokedType,MethodType samMethodType,MethodHandle implMethod,MethodType instantiatedMethodType)throws LambdaConversionException {AbstractValidatingLambdaMetafactory mf;mf = new InnerClassLambdaMetafactory(caller, invokedType,invokedName, samMethodType,implMethod, instantiatedMethodType,false, EMPTY_CLASS_ARRAY, EMPTY_MT_ARRAY);mf.validateMetafactoryArgs();return mf.buildCallSite();
}
(1)查看内部类的内容
如果想查看内部类里面的内容,可以在lambda表达式执行之前,添加:
System.setProperty("jdk.internal.lambda.dumpProxyClasses", "D://");
这个方法会将运行时生成的内部类class文件进行输出到D盘。
当该文件生成后,可以通过javap -c -p class文件名查看文件中的内容:
final class com.itheima.lambda.SourceDemo$$Lambda$1 implements java.util.function.Consumer {private com.itheima.lambda.SourceDemo$$Lambda$1();Code:0: aload_01: invokespecial #10 // Method java/lang/Object."<init>":()V4: returnpublic void accept(java.lang.Object);Code:0: aload_11: checkcast #14 // class java/lang/String4: invokestatic #20 // Method com/itheima/lambda/SourceDemo.lambda$demo$53:(Ljava/lang/String;)V7: return
}
此时可以发现编译后的Lambda表达式已经被执行。
综上所述,Lambda表达式在执行的时候,会调用LambdaMetafactory.metafactory动态的生成内部类,在方法内调用SourceDemo$&Lambda$1,内部类里的调用方法块并不是动态生成的,只是在原class里已经编译生成了一个静态的方法,内部类只需要调用该静态方法。
3、forEach分析
我们点进去这个实例,查看forEach的源码:
default void forEach(Consumer<? super T> action) {Objects.requireNonNull(action);for (T t : this) {action.accept(t);}
}
我们发现,传递的参数,就是一个Consumer。
而Consumer就是JDK8自带的Function函数:
函数式接口-lambda函数与jdk8自带的函数接口