lambda名称的由来
许多年前,在计算机出来之前,有位名叫 Alonzo Church 的逻辑学家,他想证明
什么样的数学函数是可以有效计算的(奇怪的是,当时已经存在了许多已知的函数,
但是没有人知道怎样去计算他们的值。)它使用希腊字母lambda (λ)来标记参数。
至于为什么非要用λ 是因为《数学原理》中使用‘^’来表示自由变量,所以Church就用
大写的 lambda Λ 来表示参数,但是最终还是换回了小写。
于是从那时起,带参数变量的表达式都被叫做lambda表达式。
Java不是一种函数式语言,函数无法独立的存在。可这带来的问题就是Java过于的“重”。Lambda出现之前通常情况你的参数只能传递值而不能很方便的传递行为。
Java提供了匿名类来达到传递一组行为的效果,但是匿名内部类语法过于冗余;匿名内部类中的this
和变量名容易让人产生误解;无法获取非final
的局部变量。
例:创建一个比较器
匿名类:
Comparator comparator = new Comparator() {
@Override
public int compare(Integer o1, Integer o2) {
return o1 - o2;
}
};
Lambda:
Comparator comparator = (i1, i2) -> i1 - i2;
++Lambda 表达式的结构++:
- 一个 Lambda 表达式可以有零个或多个参数
- 参数的类型既可以明确声明,也可以根据上下文来推断。例如:int a
与a
效果相同
- 所有参数需包含在圆括号内,参数之间用逗号相隔。例如:a, b
或 int a, int b
或 String a, int b, float c
- 空圆括号代表参数集为空。例如:() -> 42
- 当只有一个参数,且其类型可推导时,圆括号()
可省略。例如:a -> return a * a
- Lambda 表达式的主体可包含零条或多条语句
- 如果 Lambda 表达式的主体只有一条语句,花括号{}
可省略。匿名函数的返回类型与该主体表达式一致
- 如果 Lambda 表达式的主体包含一条以上语句,则表达式必须包含在花括号{}
中(形成代码块)。匿名函数的返回类型与代码- 块的返回类型一致,若没有返回则为空
匿名内部类有一个特点,每一个匿名类都对应一个接口。接口作为Java的一个特性,可以通过接口来作为和函数特性的连接。通过
@FunctionalInterface
来创建函数式接口可以达到一定的约束规范。再根据常用函数的特征进行抽象可以避免不必要的接口创建。
Java8通过@FunctionalInterface
来创建函数式接口,当然如果符合潜在的标准(函数式接口只能有一个抽象方法),也可以不适用注解。在自己定义函数式接口的时候,可以加上这个注解来检测是否和jdk已有的函数式接口重复,避免自己创建冗余的接口。
Java8中对函数式接口的抽象都放在了 function
包中大致分为几类:
- consumer
一个或多个入参,无返回类型
- supplier
无入参,返回对象或基础类型
- function
一个或两个入参,返回不同类型或不同的基础类型
- operator
一个或两个相同类型的入参,返回相同的类型
- predicate
一个或两个入参,返回布尔型
- run
无入参,无返回
这些函数式接口基本涵盖了日常工作中大多数对函数式接口的需求。
Lambda 本身就是类型推导
当我们定义了类似(int a) -> "abc"
的lambda表达式后,系统自动为它推导出类型。
编译器负责推导lambda的类型,它利用上下文被期待的类型当做推导的目标类型,当满足下面条件时,就会被赋予目标类型:
- 被期待的目标类型是一个函数式接口
- lambda的入参类型和数量与该接口一致
- 返回类型一致
- 抛出异常类型一致
(int a) -> "abc"
通常会被推导为 function
类型
刚才我们提到了目标对象的上下文,上下文环境一般为:++变量声明++,++返回语句++,++赋值++,++方法的参数++,++强制转换类型等++。
Lambda表达式具有显式类型和隐式类型,显式类型是由我们指定的类型,比如 Function f = (int a) -> "abc"
,隐式类型是由编译器推导而来,对于返回类型不明确的情况,即存在二义性,则我们需要手动给出目标类型。
// Object o = () -> System.out.println("123"); 编译器无法识别,提示语法错误
Object o = (Runnable) () -> System.out.println("123");
方法应用使用
::
双冒号,是lambda的简化
Class::method
instance::method
this::method
supper::method
Class::new
String[]::new
int[]::new
方法引用会生成一个lambda表达式,构造方法引用必然生成的是 supplier
类型,其他情况则需要根据具体类型推导。
文章
java虚拟机里调用方法的字节码指令有5种:
- invokestatic //调用静态方法
- invokespecial //调用私有方法、实例构造器方法、父类方法
- invokevirtual //调用实例方法
- invokeinterface //调用接口方法,会在运行时再确定一个实现此接口的对象
- ==invokedynamic== //先在运行时动态解析出调用点限定符所引用的方法,然后再执行该方法,在此之前的4条调用指令,分派逻辑是固化在java虚拟机内部的,而invokedynamic指令的分派逻辑是由用户所设定的引导方法决定的。
graph LR
a(indy)---b(call sit specifier);
b---c(bootstrap method);
c---d(callsit);
e(Method Handle)---d;
f(method)---e
==表面==
public class Illusory {
public void say(){
System.out.println("Hi");
}
}
==真实==
import java.lang.invoke.*;
public class Real {
private static void hello() {
System.out.println("Hello!");
}
public static CallSite bootstrap(MethodHandles.Lookup caller, String name, MethodType type) throws NoSuchMethodException, IllegalAccessException {
MethodHandles.Lookup lookup = MethodHandles.lookup();
Class thisClass = lookup.lookupClass();
MethodHandle mh = lookup.findStatic(thisClass, "hello", MethodType.methodType(void.class));
return new ConstantCallSite(mh.asType(type));
}
}
==变化==
import org.objectweb.asm.*;
import java.lang.invoke.CallSite;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
//Opcodes接口里定义了几乎全部的java字节码命令
public class Change implements Opcodes {
public static void main(String[] args) throws Exception {
byte[] codes = dump();
Class> clazz = new MyClassLoader().defineClass("Illusory", codes);
clazz.getMethod("say").invoke(clazz.newInstance());
}
public static byte[] dump() throws Exception {
ClassWriter classWriter = new ClassWriter(0);
MethodVisitor visitor;
classWriter.visit(V1_8, ACC_PUBLIC + ACC_SUPER, "Illusory", null, "java/lang/Object", null);
// 默认构造方法
visitor = classWriter.visitMethod(ACC_PUBLIC, "" , "()V", null, null);
visitor.visitCode();
visitor.visitVarInsn(ALOAD, 0);
visitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "" , "()V", false);
visitor.visitInsn(RETURN);
visitor.visitMaxs(1, 1);
visitor.visitEnd();
// say 方法的改变
visitor = classWriter.visitMethod(ACC_PUBLIC, "say", "()V", null, null);
visitor.visitCode();
//---------Unknown world!--------------
// visitor.visitCode();
// visitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
// visitor.visitLdcInsn("Unknown world!");
// visitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
// visitor.visitInsn(RETURN);
// visitor.visitMaxs(2, 1);
//-----------------------
//----------Real world!-------------
MethodType methodType = MethodType.methodType(CallSite.class, MethodHandles.Lookup.class, String.class,
MethodType.class);
Handle bootstrap = new Handle(Opcodes.H_INVOKESTATIC, "Real", "bootstrap", methodType.toMethodDescriptorString(), false);
visitor.visitInvokeDynamicInsn("dynamicInvoke", "()V", bootstrap);
visitor.visitInsn(RETURN);
visitor.visitMaxs(0, 1);
//-----------------------
visitor.visitEnd();
classWriter.visitEnd();
return classWriter.toByteArray();
}
private static class MyClassLoader extends ClassLoader implements Opcodes {
public Class> defineClass(String name, byte[] b) {
return super.defineClass(name, b, 0, b.length);
}
}
}
TODO
lambda最后会由编译器生成static 方法在当前类中,利用了invokedynamic命令脱离了内部类实现的优化。
例:
public class LambdaTest {
public static void main(String[] args) {
List list = Arrays.asList("a","b");
list.forEach(System.out::println);
}
}
public class LambdaTest {
public LambdaTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: iconst_2
1: anewarray #2 // class java/lang/String
4: dup
5: iconst_0
6: ldc #3 // String a
8: aastore
9: dup
10: iconst_1
11: ldc #4 // String b
13: aastore
14: invokestatic #5 // Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
17: astore_1
18: aload_1
19: getstatic #6 // Field java/lang/System.out:Ljava/io/PrintStream;
22: dup
23: invokevirtual #7 // Method java/lang/Object.getClass:()Ljava/lang/Class;
26: pop
27: invokedynamic #8, 0 // InvokeDynamic #0:accept:(Ljava/io/PrintStream;)Ljava/util/function/Consumer;
32: invokeinterface #9, 2 // InterfaceMethod java/util/List.forEach:(Ljava/util/function/Consumer;)V
37: return
}