Lambda
这个估计算是一个十分有历史感的话题了,Lambda相关的文章,也有许多了,为啥还要拿出来炒炒冷饭呢?主要是最近有对Lambda的内容进行字节码处理,一起Lambda在java/kotlin/android中,都有着不一样是完成,十分有趣,因此本文算是一个记载,让咱们一起去走进lambda的世界吧。当然,本文以java/kotlin视角去记载,在android中lambda的处理还不一样,咱们先挖个坑,看看有没有机会填上,当然,部分的我也会搀杂的一起说!
最简略的比方
比方咱们常常在写ui的时候,设置一个监听器,便是这么处理
view.setOnClickListener(v -> {
Log.e("hello","123");
});
编译后的字节码
INVOKEDYNAMIC onClick()Landroid/view/View$OnClickListener; [
// handle kind 0x6 : 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;
// arguments:
(Landroid/view/View;)V,
// handle kind 0x6 : INVOKESTATIC
这儿便是咱们要的办法
类名.lambda$myFunc$0(Landroid/view/View;)V,
(Landroid/view/View;)V
emmm,鳞次栉比,咱们先不管这个,这儿主要是INVOKEDYNAMIC的这个指令,这儿我就不再重复INVOKEDYNAMIC的由来之类的了,咱们直接来看,INVOKEDYNAMIC指令履行后的产品是啥?
生成产品类
首先产品之一,肯定是setOnClickListener里边需求的一个完成OnClickListener的目标对吧!咱们都知道INVOKEVIRTUAL会在操作数栈的履行一个消耗“目标”的操作,这个从哪里来,其实也很明显,便是从INVOKEDYNAMIC履行后被放入操作数栈的。
graph TD
INVOKEDYNAMIC --> 生出来了OnClickListener -->INVOKEVIRTUAL消耗
当然,这个生成的类仍是比较难找的,能够经过以下明=命令去翻翻
java -Djdk.internal.lambda.dumpProxyClasses 类途径
当然,在AS中也有相关的生成类,在intermediates/transform目录下,不过高版别的我找不到在哪了,如果知道的朋友也能够告知一下
调用特定办法
咱们的产品类有了,可是咱们也知道,lambda不是生成一个目标那么简略,而是要调用到里边的闭包办法,比方咱们本比方便是
v -> {
Log.e("hello","123");
}
那么咱们这个产品的办法在哪呢?
回到INVOKEDYNAMIC指令的里边,咱们看到
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;
// arguments:
(Landroid/view/View;)V,
// handle kind 0x6 : INVOKESTATIC
类名.lambda$myFunc$0(Landroid/view/View;)V,
(Landroid/view/View;)V
]
INVOKEVIRTUAL android/view/View.setOnClickListener (Landroid/view/View$OnClickListener;)V
这儿有许多新的东西,比方LambdaMetafactory(java创建运行时类),MethodHandles等,相关概念我不赘述啦!因为有比我写的更好的文章,咱们能够观看一下噢!
ASM对匿名内部类、Lambda及办法引证的Hook研讨
我这儿特地拿出来
INVOKESTATIC 类名.lambda$myFunc$0(Landroid/view/View;)V
这儿会在生成的产品类中,直接经过INVOKESTATIC办法(当然,这儿只针对咱们这个比方,后面会继续有阐明,不一定是经过INVOKESTATIC办法)办法是lambdamyFuncmyFunc0,咱们找下这个办法,能够看到,还真的有,如下
private static synthetic lambda$myFunc$0(Landroid/view/View;)V
L0
LINENUMBER 14 L0
LDC "hello"
LDC "123"
INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
POP
}
这个办法便是lambda要履行的办法,只不过在字节码中包装了一层。
至此,咱们就能够大概明白了,lambda终究干了些什么
java lambda vs Koltin lambda
java lambda
咱们刚刚有提到,生成的产品办法不一定经过INVOKESTATIC的办法调用,这也直接阐明晰,咱们的lambda的包装办法,不一定是static,即不一定是静态的。
咱们再来一文,
Lambda 设计参阅
简略来说,java lambda依照情况,生成的办法也不同,比方当时咱们的比方,它其实是一个无状况的lambda,即当时块效果域内,就能捕获到所需求的参数,所以就能直接生成一个static的办法
这儿咱们特地阐明晰块效果域,比方,下面的办法,setOnClickListener里边的lambda也依靠了一个变量a,可是他们都归于同一个块级别(函数内),
void myFunc(View view){
int a = 1;
view.setOnClickListener(v -> {
Log.e("hello","123" +a );
});
}
生成依旧是一个static办法
private static synthetic lambda$myFunc$0(ILandroid/view/View;)V
L0
LINENUMBER 15 L0
LDC "hello"
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "123"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ILOAD 0
INVOKEVIRTUAL java/lang/StringBuilder.append (I)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
POP
}
可是,如果咱们依靠当时类的一个变量,比方
类特点
public String s;
void myFunc(View view){
view.setOnClickListener(v -> {
Log.e("hello","123" +s);
});
}
此刻就生成一个当时类的实例办法,在当时类能够调用到该办法
private synthetic lambda$myFunc$0(Landroid/view/View;)V
L0
LINENUMBER 15 L0
LDC "hello"
NEW java/lang/StringBuilder
DUP
INVOKESPECIAL java/lang/StringBuilder.<init> ()V
LDC "123"
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
ALOAD 0
GETFIELD com/example/suanfa/TestCals.s : Ljava/lang/String;
INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String;
INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
POP
}
一起咱们也看到,这种办法会引进ALOAD 0,即this指针被捕获,因此,假设外层类与lambda生命周期不同步,就会导致内存泄漏的问题,这点需求留意噢!!一起咱们也要留意,并不是所有lambda都会,像上面咱们介绍的lambda就不会!
kotlin lambda
这儿特地拿kotlin 出来,是因为它有与java层不一样的点,比方相同的代码,lambda依靠了外部类的特点,生成的办法仍是一个静态的办法,而不是实例办法
var s: String = 123
fun test(view:View){
view.setOnClickListener {
Log.e("hello","$s")
}
}
字节码如下
不一样的点,选择多一个外部类的参数
private final static test$lambda-0(Lcom/example/suanfa/TestKotlin;Landroid/view/View;)V
L0
ALOAD 0
LDC "this$0"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 11 L1
LDC "hello"
ALOAD 0
GETFIELD com/example/suanfa/TestKotlin.s : Ljava/lang/String;
INVOKESTATIC java/lang/String.valueOf (Ljava/lang/Object;)Ljava/lang/String;
INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
POP
L2
LINENUMBER 12 L2
RETURN
L3
LOCALVARIABLE this$0 Lcom/example/suanfa/TestKotlin; L0 L3 0
LOCALVARIABLE it Landroid/view/View; L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2
}
相同的,同一块效果域的,也当然是静态办法
fun test(view:View){
val s = "123"
view.setOnClickListener {
Log.e("hello","$s")
}
}
如下,比起依靠了外部类的特点,没有依靠的话,自然也不必把外部类目标当作参数传入
private final static test$lambda-0(Ljava/lang/String;Landroid/view/View;)V
L0
ALOAD 0
LDC "$s"
INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkNotNullParameter (Ljava/lang/Object;Ljava/lang/String;)V
L1
LINENUMBER 11 L1
LDC "hello"
ALOAD 0
INVOKESTATIC java/lang/String.valueOf (Ljava/lang/Object;)Ljava/lang/String;
INVOKESTATIC android/util/Log.e (Ljava/lang/String;Ljava/lang/String;)I
POP
L2
LINENUMBER 12 L2
RETURN
L3
LOCALVARIABLE $s Ljava/lang/String; L0 L3 0
LOCALVARIABLE it Landroid/view/View; L0 L3 1
MAXSTACK = 2
MAXLOCALS = 2
}
因此,咱们能够经过这两个差异,能够做一些特定的字节码逻辑。
总结
lambda的水仍是挺深的,咱们能够经过本文,去初步了解一些lambda的常识,一起咱们也需求留意,在android中,也为了兼容lambda,做了一定的骚操作,比方咱们常说的d8会对desuger做了一些操作等等。一起android的生成产品类,也会做单例的优化,这在一些场景会有不一样的坑,咱们之后再见啦!