为了支持函数式编程,Java 8引入了Lambda表达式,那么在Java 8中到底是如何实现Lambda表达式的呢? 为什么使用Lambda表达式呢?Lambda表达式经过编译之后,到底会生成什么东西呢?
定义
Lambda 是一个匿名函数,我们可以把 Lambda表达式理解为是一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
把Lambda表达式理解为简洁地表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。
匿名——我们说匿名,是因为它不像普通的方法那样有一个明确的名称:写得少而想得多
函数——我们说它是函数,是因为Lambda函数不像方法那样属于某个特定的类。但和方
法一样,Lambda有参数列表、函数主体、返回类型,还可能有可以抛出的异常列表。
传递——Lambda表达式可以作为参数传递给方法或存储在变量中。
简洁——无需像匿名类那样写很多模板代码。
Lambda表达式语法
Lambda表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “->” , 该操作符被称 为 Lambda 操作符或剪头操作符。它将 Lambda 分为 两个部分:
左侧: 指定了 Lambda 表达式需要的所有参数
右侧: 指定了 Lambda 体,即 Lambda 表达式要执行的功能。
(1)语法格式一:无参,无返回值,Lambda 体只需一条语句
示例:Runnable r1 = () -> System.out.println("Hello Lambda!");
(2)语法格式二:Lambda 需要一个参数
示例:Consumer<String> con = (x) -> System.out.println(x);
(3)语法格式三:Lambda 只需要一个参数时,参数的小括号可以省略
示例:Consumer<String> con = x -> System.out.println(x);
(4)语法格式四:Lambda 需要两个参数,并且有返回值
示例:
Comparator<Integer> com = (x, y) -> {
System.out.println("函数式接口");
return Integer.compare(x, y);
};
(5)语法格式五:当 Lambda 体只有一条语句时,return 与大括号可以省略
示例:Comparator<Integer> com = (x, y) -> Integer.compare(x, y);
(6)Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
示例:
Comparator<Integer> com = (Integer x,Integer y) -> { //Integer 类型可以省略
System.out.println("函数式接口");
return Integer.compare(x, y);
};
类型推断:Lambda 表达式中的参数类型都是由编译器推断 得出的。 Lambda 表达式中无需指定类型,程序依然可 以编译,这是因为 javac 根据程序的上下文,在后台 推断出了参数的类型。 Lambda 表达式的类型依赖于上 下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”
**
什么情况下使用Lambda表达式和如何使用 Lambda?
函数式接口: 函数式接口就是只定义一个抽象方法的接口
下面三个接口都是函数式接口:
@FunctionalInterface
public interface Comparator<T> {
int compare(T o1, T o2);
}
@FunctionalInterface
public interface Callable<V>{
V call();
}
public interface PrivilegedAction<T> {
T run();
}
用函数式接口可以干什么呢?Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体的实例)。
案例:
public static void main(String[] args)
{
Integer[] datas= {1,4,3,2};
//匿名内部类写法
Comparator<Integer> comp1 = new Comparator<Integer>()
{
@Override
public int compare(Integer o1, Integer o2)
{
return o1 - o2;
}
};
//lambda表达式写法
Comparator<Integer> comp2 = (o1,o2) -> o1 - o2;
//方法引用的写法
Comparator<Integer> comp3 = Integer::compareTo;
//排序操作
Arrays.sort(datas,comp1);//comp1,comp2,comp3参数都可以
System.out.println(Arrays.toString(datas));//[1, 2, 3, 4]
}
分析上面的案例:
comp1对象是没有Lambda表达式之前的时候创建一个Comparator对象的方式,然后把comp1对象传递给Arrays.sort()方法,很显然的创建;
comp2对象是用了Lambda表达式来创建对象,参数传递的Lambda表达式(用Lambda前提是参数是一个函数式接口哦,详细参考java8------函数式接口),其实内部是调用了该类内部私有的静态方法,后面会分析;
comp3对象是利用的方法引用来实现,详细参考java8-----方法引用。
Lambda表达式的存在在简化了匿名内部类的写法(功能是相同的,并且性能比匿名内部类的方式更好),前提是该参数类是函数式接口,有了Lambda表达式的存在代码书写更加简洁,可读性好。而方法引用存在更加方便了lambda表达式的书写。
lambda表达式的原理分析:
参考:https://blog.csdn.net/f641385712/article/details/81486280
为了支持函数式编程,Java 8引入了Lambda表达式,那么在Java 8中到底是如何实现Lambda表达式的呢? Lambda表达式经过编译之后,到底会生成什么东西呢?
Java 8中每一个Lambda表达式必须有一个函数式接口与之对应。
或许我们可以推测:Lambda表达式是不是转化成与之对应的函数式接口的一个实现类呢,然后通过多态的方式调用子类的实现呢,如下面代码是一个Lambda表达式的样例:
@FunctionalInterface
interface Print<T> {
public void print(T x);
}
public class Lambda {
//待测试的静态方法
public static void printString(String s, Print<String> print) {
print.print(s);
}
public static void main(String[] args) {
PrintString("test", (x) -> System.out.println(x));
}
}
如果根据我们的推测,Java编译器编译后,肯定会出现一个类似Lambda$$0这样的类或者内部类,从而肯定存在一个.class文件。因为假如符合我们的预想,其实结果可以达到一模一样?
但是Java8中的lambda真是这么实现的吗?
为了探究Lambda表达式是如何实现的,就得需要研究Lambda表过式最终转化成的字节码文件,这就需要jdk的bin目录下的一个字节码查看工具及反编译工具
javap -p Lambda.class (命令中的-p表示输出所有类及成员)
运行上面的命令后,得的结果如下所示:
public class Lambda {
public Lambda();
public static void printString(java.lang.String, Print<java.lang.String>);
public static void main(java.lang.String[]);
//显然,这个方法是编译后多出的
private static void lambda$0(java.lang.String);
}
由上面的代码可以看出编译器会根据Lambda表达式生成一个私有的静态方法(这点非常重要,就是为什么lambda表达式比匿名内部类效率高的直接原因),注意,在这里说的是生成,而不是等价
private static void lambda$0(java.lang.String);
验证一下
为了验证上面的转化是否正确? 我们在代码中定义一个lambda$0这个方法:
public static void PrintString(String s, Print<String> print) {
print.print(s);
}
private static void lambda$0(String s) {
}
public static void main(String[] args) {
PrintString("test", (x) -> System.out.println(x));
}
如上,我们代码在编译期间不抱错。但是,但是在运行期间就报错了。
Exception in thread "main" java.lang.ClassFormatError: Duplicate method name&signature in class file Lambda
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:760)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:495)
很明显,报错的额原因是因为存在两个lambda$0函数。
通过javap对上述错误代码进行反编译,反编译之后输出的类的成员如下所示:
public class Lambda {
public Lambda();
public static void PrintString(java.lang.String, Print<java.lang.String>);
private static void lambda$0(java.lang.String);
public static void main(java.lang.String[]);
private static void lambda$0(java.lang.String);
}
竟然发现lambda$0出现了两次,那么在代码运行的时候,就不知道去调用哪个,因此就会抛错。
提一句:
java底层处理lambda,其实非常的强大和巧妙。增加了不少类来处理lambda表达式。这里最重要的一个是LambdaMetafactory
里面有个方法:
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();
}
所有的lambda在运行时会进入这个函数。所以我们在这个方法里面打断点,来获取更多的信息。
结论
Lambda表达式在Java 8中首先会生成一个私有的静态函数,这个私有的静态函数干的就是Lambda表达式里面的内容