第一次接触lambda表达式时,感受这个东西挺神奇的(高逼格),一个()加->就能传递一段代码,当时公司项目中接手同事的代码,本身也对java8的特性不了解,看的也是一头雾水,以后就赶快看了下《java8实战》这本书,决定写一个java8特性系列的博客,既加深本身的印象,还能跟你们分享一下,但愿你们多多指教😄。java
Lambda是一个匿名函数,咱们能够把Lambda表达式理解为是一段能够传递的代码(将代码像参数同样进行传递,称为行为参数化)。Lambda容许把函数做为一个方法的参数(函数做为参数传递进方法中),要作到这一点就须要了解,什么是函数式接口,这里先不作介绍,等下一篇在讲解。bash
首先先看一下lambda长什么样? 正常写法:ide
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello lambda");
}
}).start();
复制代码
lambda写法:函数
new Thread(
() -> System.out.println("hello lambda")
).start();
复制代码
怎么样?是否是感受很简洁,没错,这就是lambda的魅力,他可让你写出来的代码更简单、更灵活。工具
可选类型声明:
不须要声明参数类型,编译器能够统一识别参数值。也就说(s) -> System.out.println(s)和 (String s) -> System.out.println(s)是同样的编译器会进行类型推断因此不须要添加参数类型。可选的参数圆括号:
一个参数无需定义圆括号,但多个参数须要定义圆括号。例如:可选的大括号:
若是主体包含了一个语句,就不须要使用大括号。
可选的返回关键字:
若是主体只有一个表达式返回值则编译器会自动返回值,大括号须要指定明表达式返回了一个数值。Lambda体不加{ }就不用写return:ui
Comparator<Integer> com = (x, y) -> Integer.compare(y, x);
复制代码
Lambda体加上{ }就须要添加return:spa
Comparator<Integer> com = (x, y) -> {
int compare = Integer.compare(y, x);
return compare;
};
复制代码
上面咱们看到了一个lambda表达式应该怎么写,但lambda中有一个重要特征是可选参数类型声明
,就是说不用写参数的类型,那么为何不用写呢?它是怎么知道的参数类型呢?这就涉及到类型推断了。code
java8的泛型类型推断改进:cdn
List<Person> ps = ...
Stream<String> names = ps.stream().map(p -> p.getName());
复制代码
在上面的代码中,ps的类型是List<Person>
,因此ps.stream()的返回类型是Stream<Person>
。map()方法接收一个类型为Function<T, R>的函数式接口,这里T的类型便是Stream元素的类型,也就是Person,而R的类型未知。因为在重载解析以后lambda表达式的目标类型仍然未知,咱们就须要推导R的类型:经过对lambda表达式lambda进行类型检查,咱们发现lambda体返回String,所以R的类型是String,于是map()返回Stream<String>
。绝大多数状况下编译器都能解析出正确的类型,但若是碰到没法解析的状况,咱们则须要:对象
<String>
map(p -> p.getName()))方法引用是用来直接访问类或者实例已经存在的方法或构造方法,提供了一种引用而不执行方法的方式。是一种更简洁更易懂的Lambda表达式,当Lambda表达式中只是执行一个方法调用时,直接使用方法引用的形式可读性更高一些。 方法引用使用 “ :: ” 操做符来表示,左边是类名或实例名,右边是方法名。 (注意:方法引用::右边的方法名是不须要加()的,例:User::getName)
方法引用的几种形式:
例如:
Consumer<String> consumer = (s) -> System.out.println(s);
等同于:
Consumer<String> consumer = System.out::println;
例如:
Function<String, Integer> stringToInteger = (String s) -> Integer.parseInt(s);
等同于:
Function<String, Integer> stringToInteger = Integer::parseInt;
例如:
BiPredicate<List<String>, String> contains = (list, element) -> list.contains(element);
等同于:
BiPredicate<List<String>, String> contains = List::contains;
复制代码
注意:
语法格式:类名::new
例如:
Supplier<User> supplier = ()->new User();
等同于:
Supplier<User> supplier = User::new;
复制代码
注意:
须要调用的构造器方法与函数式接口中抽象方法的参数列表保持一致。
研究了半天Lambda怎么写,但是它的原理是什么?咱们简单看个例子,看看真相究竟是什么:
public class StreamTest {
public static void main(String[] args) {
printString("hello lambda", (String s) -> System.out.println(s));
}
public static void printString(String s, Print<String> print) {
print.print(s);
}
}
@FunctionalInterface
interface Print<T> {
public void print(T t);
}
复制代码
上面的代码自定义了一个函数式接口,定义一个静态方法而后用这个函数式接口来接收参数。编写完这个类之后,咱们到终端界面javac进行编译,而后用javap(javap是jdk自带的反解析工具。它的做用就是根据class字节码文件,反解析出当前类对应的code区(汇编指令)、本地变量表、异常表和代码行偏移量映射表、常量池等等信息。)进行解析,以下图:
lambda$main$0
静态方法,这个静态方法实现了Lambda表达式的逻辑,如今咱们知道原来Lambda表达式被编译成了一个静态方法,那么这个静态方式是怎么调用的呢?咱们继续进行public com.lxs.stream.StreamTest();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 7: 0
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #2 // String hello lambda
2: invokedynamic #3, 0 // InvokeDynamic #0:print:()Lcom/lxs/stream/Print;
7: invokestatic #4 // Method printString:(Ljava/lang/String;Lcom/lxs/stream/Print;)V
10: return
LineNumberTable:
line 10: 0
line 12: 10
public static void printString(java.lang.String, com.lxs.stream.Print<java.lang.String>);
descriptor: (Ljava/lang/String;Lcom/lxs/stream/Print;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=2
0: aload_1
1: aload_0
2: invokeinterface #5, 2 // InterfaceMethod com/lxs/stream/Print.print:(Ljava/lang/Object;)V
7: return
LineNumberTable:
line 15: 0
line 16: 7
Signature: #19 // (Ljava/lang/String;Lcom/lxs/stream/Print<Ljava/lang/String;>;)V
private static void lambda$main$0(java.lang.String);
descriptor: (Ljava/lang/String;)V
flags: ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
Code:
stack=2, locals=1, args_size=1
0: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #7 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
LineNumberTable:
line 10: 0
}
SourceFile: "StreamTest.java"
InnerClasses:
public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#28 (Ljava/lang/Object;)V
#29 invokestatic com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
#30 (Ljava/lang/String;)V
复制代码
这里只贴出了一部分的字节码结构,因为常量池定义太长了,就没有粘贴。
InnerClasses:
public static final #58= #57 of #61; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
BootstrapMethods:
0: #27 invokestatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Method arguments:
#28 (Ljava/lang/Object;)V
#29 invokestatic com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
#30 (Ljava/lang/String;)V
复制代码
经过这段字节码结构发现是要生成一个内部类,使用invokestatic调用了一个LambdaMetafactory.metafactory方法,并把lambda$main$0
做为参数传了进去,咱们来看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();
}
复制代码
在buildCallSite的函数中,是函数spinInnerClass 构建了这个内部类。也就是生成了一个StreamTest$$Lambda$1.class这样的内部类,这个类是在运行的时候构建的,并不会保存在磁盘中。
@Override
CallSite buildCallSite() throws LambdaConversionException {
final Class<?> innerClass = spinInnerClass();
如下省略。。。
}
复制代码
若是想看到这个构建的类,能够经过设置环境参数 System.setProperty("jdk.internal.lambda.dumpProxyClasses", " . "); 会在你指定的路径 . 当前运行路径上生成这个内部类。咱们看下一下生成的类长什么样
咱们在javap -v -p StreamTest$$Lambda$1.class看下:
{
private com.lxs.stream.StreamTest$$Lambda$1();
descriptor: ()V
flags: ACC_PRIVATE
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
public void print(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC
Code:
stack=1, locals=2, args_size=2
0: aload_1
1: checkcast #15 // class java/lang/String
4: invokestatic #21 // Method com/lxs/stream/StreamTest.lambda$main$0:(Ljava/lang/String;)V
7: return
RuntimeVisibleAnnotations:
0: #13()
}
复制代码
发如今重写的parint方法中使用invokestatic指令调用了lambda$main$0方法。
总结: 这样实现了Lambda表达式,使用invokedynamic指令,运行时调用LambdaMetafactory.metafactory动态的生成内部类,实现了函数式接口,并在重写函数式接口中的方法,在方法内调用lambda$main$0
,内部类里的调用方法块并非动态生成的,只是在原class里已经编译生成了一个静态的方法,内部类只须要调用该静态方法。
你们看后辛苦点个赞点关注哦!后续还会后更多的博客。 若有错误,烦请指正。