Lambda表达式初体验
简介
Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。
——《百度百科》
Java
对于Lambda表达式的支持是从JDK8开始的,它来源于数学中的λ演算,是一套关于函数\(f(x)\)定义、输入量、输出量的计算方案,简化了匿名函数的编写,使代码变得简洁。
举个例子
首先声明一个接口Factory
和一个User
类。
public interface Factory {
Object getObject();
}
public class User {
private String name;
private int age;
public User() {}
public User(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "User{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
按照传统的方式,想要实现Factory
接口中的抽象方法,有两种方法:
- 子类实现接口
- 匿名内部类
对于第一种方式,代码如下:
public class SubClass implements Factory {
@Override
public Object getObject() {
return new User("tom", 20);
}
}
public class Code01_LambdaTest {
public static void main(String[] args) {
// 子类实现接口
Factory factory = new SubClass();
System.out.println("user = " + user);
}
}
对于第二种方式,代码如下:
public class Code01_LambdaTest {
public static void main(String[] args) {
// 匿名内部类
Factory factory = new Factory() {
@Override
public Object getObject() {
return new User("John", 18);
}
};
System.out.println("user = " + user);
}
}
分析上面的代码,发现接口Factory
中仅仅只有一个抽象方法,我们的目的是为了获取User
类的对象这么一个简单的操作,却需要书写这么多代码,最最核心的其实就是这一句return new User("John", 18)
,有没有更加简洁的书写方式实现上面的需求呢?
第三种方法,就是我们要介绍的Lambda表达式
。请看代码:
public class Code01_LambdaTest {
public static void main(String[] args) {
// lambda表达式
Factory factory = () -> new User("Mike", 30);
System.out.println("user = " + user);
}
}
Lambda表达式的语法格式
Lambda表达式应用举例
Lambda的底层实现
它的本质是函数式接口的匿名子类的匿名对象。
import java.util.Arrays;
import java.util.List;
public class LambdaPrinciple {
public static void main(String[] args) {
List<String> list = Arrays.asList("I", "Love", "You");
list.forEach(s -> System.out.println(s));
}
}
编译后的字节码文件
执行反编译命令:java -jar cfr-0.145.jar LambdaPrinciple.class --decodelambdas false
import java.io.PrintStream;
import java.lang.invoke.LambdaMetafactory;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
public class LambdaPrinciple {
public static void main(String[] args) {
List<String> list = Arrays.asList("I", "Love", "You");
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 s) {
System.out.println(s);
}
}
从上面可以看到,Lambda表达式最终被编译成lambda$main$0
静态方法去执行。
接下来找到LambdaMetafactory.java
,看下它的metafactory
方法
public final class LambdaMetafactory {
...
// LambdaMetafactory bootstrap methods are startup sensitive, and may be
// special cased in java.lang.invoke.BootstrapMethodInvoker to ensure
// methods are invoked with exact type information to avoid generating
// code for runtime checks. Take care any changes or additions here are
// reflected there as appropriate.
/**
* Facilitates the creation of simple "function objects" that implement one
* or more interfaces by delegation to a provided {@link MethodHandle},
* after appropriate type adaptation and partial evaluation of arguments.
* Typically used as a <em>bootstrap method</em> for {@code invokedynamic}
* call sites, to support the <em>lambda expression</em> and <em>method
* reference expression</em> features of the Java Programming Language.
*
* <p>This is the standard, streamlined metafactory; additional flexibility
* is provided by {@link #altMetafactory(MethodHandles.Lookup, String, MethodType, Object...)}.
* A general description of the behavior of this method is provided
* {@link LambdaMetafactory above}.
*
* <p>When the target of the {@code CallSite} returned from this method is
* invoked, the resulting function objects are instances of a class which
* implements the interface named by the return type of {@code factoryType},
* declares a method with the name given by {@code interfaceMethodName} and the
* signature given by {@code interfaceMethodType}. It may also override additional
* methods from {@code Object}.
*
* @param caller Represents a lookup context with the accessibility
* privileges of the caller. Specifically, the lookup context
* must have {@linkplain MethodHandles.Lookup#hasFullPrivilegeAccess()
* full privilege access}.
* When used with {@code invokedynamic}, this is stacked
* automatically by the VM.
* @param interfaceMethodName The name of the method to implement. When used with
* {@code invokedynamic}, this is provided by the
* {@code NameAndType} of the {@code InvokeDynamic}
* structure and is stacked automatically by the VM.
* @param factoryType The expected signature of the {@code CallSite}. The
* parameter types represent the types of capture variables;
* the return type is the interface to implement. When
* used with {@code invokedynamic}, this is provided by
* the {@code NameAndType} of the {@code InvokeDynamic}
* structure and is stacked automatically by the VM.
* @param interfaceMethodType Signature and return type of method to be
* implemented by the function object.
* @param implementation A direct method handle describing the implementation
* method which should be called (with suitable adaptation
* of argument types and return types, and with captured
* arguments prepended to the invocation arguments) at
* invocation time.
* @param dynamicMethodType The signature and return type that should
* be enforced dynamically at invocation time.
* In simple use cases this is the same as
* {@code interfaceMethodType}.
* @return a CallSite whose target can be used to perform capture, generating
* instances of the interface named by {@code factoryType}
* @throws LambdaConversionException If {@code caller} does not have full privilege
* access, or if {@code interfaceMethodName} is not a valid JVM
* method name, or if the return type of {@code factoryType} is not
* an interface, or if {@code implementation} is not a direct method
* handle referencing a method or constructor, or if the linkage
* invariants are violated, as defined {@link LambdaMetafactory above}.
* @throws NullPointerException If any argument is {@code null}.
* @throws SecurityException If a security manager is present, and it
* <a href="MethodHandles.Lookup.html#secmgr">refuses access</a>
* from {@code caller} to the package of {@code implementation}.
*/
public static CallSite metafactory(MethodHandles.Lookup caller,
String interfaceMethodName,
MethodType factoryType,
MethodType interfaceMethodType,
MethodHandle implementation,
MethodType dynamicMethodType)
throws LambdaConversionException {
AbstractValidatingLambdaMetafactory mf;
mf = new InnerClassLambdaMetafactory(Objects.requireNonNull(caller),
Objects.requireNonNull(factoryType),
Objects.requireNonNull(interfaceMethodName),
Objects.requireNonNull(interfaceMethodType),
Objects.requireNonNull(implementation),
Objects.requireNonNull(dynamicMethodType),
false,
EMPTY_CLASS_ARRAY,
EMPTY_MT_ARRAY);
mf.validateMetafactoryArgs();
return mf.buildCallSite();
}
}
metafactory
方法参数的说明:
接下来看下new InnerClassLambdaMetafactory()
的代码,它的作用是创建内部类
public InnerClassLambdaMetafactory(MethodHandles.Lookup caller,
MethodType factoryType,
String interfaceMethodName,
MethodType interfaceMethodType,
MethodHandle implementation,
MethodType dynamicMethodType,
boolean isSerializable,
Class<?>[] altInterfaces,
MethodType[] altMethods)
throws LambdaConversionException {
super(caller, factoryType, interfaceMethodName, interfaceMethodType,
implementation, dynamicMethodType,
isSerializable, altInterfaces, altMethods);
implMethodClassName = implClass.getName().replace('.', '/');
implMethodName = implInfo.getName();
implMethodDesc = implInfo.getMethodType().toMethodDescriptorString();
constructorType = factoryType.changeReturnType(Void.TYPE);
lambdaClassName = lambdaClassName(targetClass);
// If the target class invokes a protected method inherited from a
// superclass in a different package, or does 'invokespecial', the
// lambda class has no access to the resolved method. Instead, we need
// to pass the live implementation method handle to the proxy class
// to invoke directly. (javac prefers to avoid this situation by
// generating bridges in the target class)
useImplMethodHandle = (Modifier.isProtected(implInfo.getModifiers()) &&
!VerifyAccess.isSamePackage(targetClass, implInfo.getDeclaringClass())) ||
implKind == H_INVOKESPECIAL;
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
int parameterCount = factoryType.parameterCount();
if (parameterCount > 0) {
argNames = new String[parameterCount];
argDescs = new String[parameterCount];
for (int i = 0; i < parameterCount; i++) {
argNames[i] = "arg$" + (i + 1);
argDescs[i] = BytecodeDescriptor.unparse(factoryType.parameterType(i));
}
} else {
argNames = argDescs = EMPTY_STRING_ARRAY;
}
}
值得注意的是cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
,它的作用是构造一个新的ClassWriter对象,写字节码文件,这其实就是ASM技术。
final String dumpProxyClassesKey = "jdk.internal.lambda.dumpProxyClasses";
final class LambdaPrinciple$$Lambda$1 implements Consumer {
private LambdaPrinciple$$Lambda$1() {
}
@LambdaForm.Hidden
public void accept(Object object) {
LambdaPrinciple.lambda$main$0((String)object);
}
}
使用Java反编译命令javap -p -v .\LambdaPrinciple
Last modified 2022-10-9; size 1566 bytes
MD5 checksum 2c03c5e9129bf4b9bdd72edde1fc98ff
Compiled from "LambdaPrinciple.java"
public class class04_lambda_principle.LambdaPrinciple
minor version: 0
major version: 61
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Methodref #2.#3 // java/lang/Object."<init>":()V
#2 = Class #4 // java/lang/Object
#3 = NameAndType #5:#6 // "<init>":()V
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Class #8 // java/lang/String
#8 = Utf8 java/lang/String
#9 = String #10 // I
#10 = Utf8 I
#11 = String #12 // Love
#12 = Utf8 Love
#13 = String #14 // You
#14 = Utf8 You
#15 = Methodref #16.#17 // java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
#16 = Class #18 // java/util/Arrays
#17 = NameAndType #19:#20 // asList:([Ljava/lang/Object;)Ljava/util/List;
#18 = Utf8 java/util/Arrays
#19 = Utf8 asList
#20 = Utf8 ([Ljava/lang/Object;)Ljava/util/List;
#21 = InvokeDynamic #0:#22 // #0:accept:()Ljava/util/function/Consumer;
#22 = NameAndType #23:#24 // accept:()Ljava/util/function/Consumer;
#23 = Utf8 accept
#24 = Utf8 ()Ljava/util/function/Consumer;
#25 = InterfaceMethodref #26.#27 // java/util/List.forEach:(Ljava/util/function/Consumer;)V
#26 = Class #28 // java/util/List
#27 = NameAndType #29:#30 // forEach:(Ljava/util/function/Consumer;)V
#28 = Utf8 java/util/List
#29 = Utf8 forEach
#30 = Utf8 (Ljava/util/function/Consumer;)V
#31 = Fieldref #32.#33 // java/lang/System.out:Ljava/io/PrintStream;
#32 = Class #34 // java/lang/System
#33 = NameAndType #35:#36 // out:Ljava/io/PrintStream;
#34 = Utf8 java/lang/System
#35 = Utf8 out
#36 = Utf8 Ljava/io/PrintStream;
#37 = Methodref #38.#39 // java/io/PrintStream.println:(Ljava/lang/String;)V
#38 = Class #40 // java/io/PrintStream
#39 = NameAndType #41:#42 // println:(Ljava/lang/String;)V
#40 = Utf8 java/io/PrintStream
#41 = Utf8 println
#42 = Utf8 (Ljava/lang/String;)V
#43 = Class #44 // class04_lambda_principle/LambdaPrinciple
#44 = Utf8 class04_lambda_principle/LambdaPrinciple
#45 = Utf8 Code
#46 = Utf8 LineNumberTable
#47 = Utf8 LocalVariableTable
#48 = Utf8 this
#49 = Utf8 Lclass04_lambda_principle/LambdaPrinciple;
#50 = Utf8 main
#51 = Utf8 ([Ljava/lang/String;)V
#52 = Utf8 args
#53 = Utf8 [Ljava/lang/String;
#54 = Utf8 list
#55 = Utf8 Ljava/util/List;
#56 = Utf8 LocalVariableTypeTable
#57 = Utf8 Ljava/util/List<Ljava/lang/String;>;
#58 = Utf8 lambda$main$0
#59 = Utf8 s
#60 = Utf8 Ljava/lang/String;
#61 = Utf8 SourceFile
#62 = Utf8 LambdaPrinciple.java
#63 = Utf8 BootstrapMethods
#64 = MethodHandle #6:#65 // 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;
#65 = Methodref #66.#67 // 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;
#66 = Class #68 // java/lang/invoke/LambdaMetafactory
#67 = NameAndType #69:#70 // 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;
#68 = Utf8 java/lang/invoke/LambdaMetafactory
#69 = Utf8 metafactory
#70 = Utf8 (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;
#71 = MethodType #72 // (Ljava/lang/Object;)V
#72 = Utf8 (Ljava/lang/Object;)V
#73 = MethodHandle #6:#74 // invokestatic class04_lambda_principle/LambdaPrinciple.lambda$main$0:(Ljava/lang/String;)V
#74 = Methodref #43.#75 // class04_lambda_principle/LambdaPrinciple.lambda$main$0:(Ljava/lang/String;)V
#75 = NameAndType #58:#42 // lambda$main$0:(Ljava/lang/String;)V
#76 = MethodType #42 // (Ljava/lang/String;)V
#77 = Utf8 InnerClasses
#78 = Class #79 // java/lang/invoke/MethodHandles$Lookup
#79 = Utf8 java/lang/invoke/MethodHandles$Lookup
#80 = Class #81 // java/lang/invoke/MethodHandles
#81 = Utf8 java/lang/invoke/MethodHandles
#82 = Utf8 Lookup
{
public class04_lambda_principle.LambdaPrinciple();
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 10: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lclass04_lambda_principle/LambdaPrinciple;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=4, locals=2, args_size=1
0: iconst_3
1: anewarray #7 // class java/lang/String
4: dup
5: iconst_0
6: ldc #9 // String I
8: aastore
9: dup
10: iconst_1
11: ldc #11 // String Love
13: aastore
14: dup
15: iconst_2
16: ldc #13 // String You
18: aastore
19: invokestatic #15 // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
22: astore_1
23: aload_1
24: invokedynamic #21, 0 // InvokeDynamic #0:accept:()Ljava/util/function/Consumer;
29: invokeinterface #25, 2 // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
34: return
LineNumberTable:
line 12: 0
line 13: 23
line 14: 34
LocalVariableTable:
Start Length Slot Name Signature
0 35 0 args [Ljava/lang/String;
23 12 1 list Ljava/util/List;
LocalVariableTypeTable:
Start Length Slot Name Signature
23 12 1 list Ljava/util/List<Ljava/lang/String;>;
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 #31 // Field java/lang/System.out:Ljava/io/PrintStream;
3: aload_0
4: invokevirtual #37 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
7: return
LineNumberTable:
line 13: 0
LocalVariableTable:
Start Length Slot Name Signature
0 8 0 s Ljava/lang/String;
}
SourceFile: "LambdaPrinciple.java"
BootstrapMethods:
0: #64 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:
#71 (Ljava/lang/Object;)V
#73 invokestatic class04_lambda_principle/LambdaPrinciple.lambda$main$0:(Ljava/lang/String;)V
#76 (Ljava/lang/String;)V
InnerClasses:
public static final #82= #78 of #80; //Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
Java7开始支持动态编译