簡述: 今天帶來的是Kotlin淺談系列第七彈,上篇部落格我們聊到關於Kotlin中的lambda表示式的一些語法規則和基本使用。然而我們並沒有聊到Kotlin的lambda表示式的本質是什麼?我們都知道使用Kotlin來開發Android,最終都會編譯成位元組碼檔案.class,然後位元組碼檔案run到JVM上,最後整個應用跑起來。
- 1、為什麼需要去對lambda表示式位元組碼分析?(why)
- 2、lambda表示式實質原理是什麼?(what)
- 3、lambda表示式位元組碼檢視工具的使用
- 4、kotlin中@Metadata註解詳解
- 5、如何去分析lambda表示式位元組碼(how)
- 6、使用lambda表示式時的效能優化
- 7、使用lambda表示式需要注意哪些問題
一、為什麼需要去對lambda表示式位元組碼分析?
Kotlin中的lambda表示式給使用者的感知它就是一個很簡潔的語法糖,但是在簡潔語法糖背後內容你有所瞭解嗎?學會分析lambda表示式編譯成位元組碼的整個過程,會對高效率地去使用lambda表示式會有很大幫助,否則就會很容出現lambda表示式濫用的情況,這種情況非常影響程式效能。所以需要從真正本質上去完全理解lambda是什麼?以及它是如何編譯成對應class。
二、lambda表示式實質原理
Kotlin中的lambda表示式實際上最後會編譯為一個class類,這個類會去繼承Kotlin中Lambda的抽象類(在kotlin.jvm.internal包中)並且實現一個FunctionN(在kotlin.jvm.functions包中)的介面(這個N是根據lambda表示式傳入引數的個數決定的,目前介面N的取值為 0 <= N <= 22,也就是lambda表示式中函式傳入的引數最多也只能是22個),這個Lambda抽象類是實現了FunctionBase介面,該介面中有兩個方法一個是getArity()獲取lambda引數的元數,toString()實際上就是列印出Lambda表示式型別字串,獲取Lambda表示式型別字串是通過Java中Reflection類反射來實現的。FunctionBase介面繼承了Function,Serializable介面。來看一個簡單的lambda例子
package com.mikyou.kotlin.lambda.simple
typealias Sum = (Int, Int, Int) -> Int
fun main(args: Array<String>) {
val sum: Sum = { a, b, c ->//定義一個很簡單的三個數求和的lambda表示式
a + b + c
}
println(sum.invoke(1, 2, 3))
}
複製程式碼
lambda呼叫處反編譯後的程式碼
package com.mikyou.kotlin.lambda.simple;
import kotlin.Metadata;
import kotlin.jvm.functions.Function3;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 10},
bv = {1, 0, 2},
k = 2,
d1 = {"\u0000\u001e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0010\b\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005*:\u0010\u0006\"\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u00072\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u0007¨\u0006\t"},
d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "Sum", "Lkotlin/Function3;", "", "production sources for module Lambda_main"}
)
public final class SumLambdaKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
Function3 sum = (Function3)null.INSTANCE;//例項化的是FunctionN介面中Function3,因為有三個引數
int var2 = ((Number)sum.invoke(1, 2, 3)).intValue();
System.out.println(var2);
}
}
複製程式碼
lambda反編譯後的程式碼
package com.mikyou.kotlin.lambda.simple;
import kotlin.jvm.internal.Lambda;
@kotlin.Metadata(mv = {1, 1, 10}, bv = {1, 0, 2}, k = 3, d1 = {"\000\n\n\000\n\002\020\b\n\002\b\004\020\000\032\0020\0012\006\020\002\032\0020\0012\006\020\003\032\0020\0012\006\020\004\032\0020\001H\n¢\006\002\b\005"}, d2 = {"<anonymous>", "", "a", "b", "c", "invoke"})
final class SumLambdaKt$main$sum$1 extends Lambda implements kotlin.jvm.functions.Function3<Integer, Integer, Integer, Integer> {
public final int invoke(int a, int b, int c) {
return a + b + c;
}
public static final SumLambdaKt$main$sum$1 INSTANCE =new SumLambdaKt$main$sum$1();
SumLambdaKt$main$sum$1() {
super(3);//這個super傳入3,也就是前面getArity獲得引數的元數和函式引數個數一致
}
}
複製程式碼
三、lambda表示式位元組碼檢視工具的使用
既然是分析位元組碼class檔案,所以必須得有個位元組碼檢視工具,你可以使用JD-GUI或者其他的,我這裡使用的是BytecodeViewer,它核心實現也是基於JD-GUI的。任意選擇一款位元組碼檢視工具都可以,這裡是BytecodeViewer的下載地址有需要可以去下載,它的使用也是非常簡單的。
- 1、下載完畢後,開啟主介面(介面有點粗糙,這個可以忽略)
- 2、然後,只需要把相應的.class檔案拖入檔案選擇區,就可以檢視相應反編譯的Java程式碼和位元組碼
四、kotlin中@Metadata註解詳解
從剛剛反編譯的程式碼,有個@Metadata註解很是引入注意,它到底是個啥?每個Kotlin程式碼反編譯成Java程式碼都會有這個@Metadata註解。為了更好地去理解這個位元組碼生成的過程,我覺得有必要去了解一下,它們每一個的含義。
1、@Metadata註解介紹及生成流程
kotlin中的@Metadata註解是一個很特殊的註解,它記錄了Kotlin程式碼中的一些資訊,比如 class 的可見性,function 的返回值,引數型別,property 的 lateinit,nullable 的屬性,typealias型別別名宣告等。我們都知道Kotlin程式碼最終都要轉化成Java的位元組碼的,然後執行JVM上。但是Kotlin程式碼和Java程式碼差別還是很大的,一些Kotlin特殊語言特性是獨有的(比如lateinit, nullable, typealias),所以需要記錄一些資訊來標識Kotlin中的一些特殊語法資訊。最終這些資訊都是有kotlinc編譯器生成,並以註解的形式存在於位元組碼檔案中。
2、@Metadata註解的狀態
通過上面轉化圖可得知,@Metadata註解會一直儲存在class位元組碼中,也就是這個註解是一個執行時的註解,在RunTime的時候會一直保留,那麼可以通過反射可以拿到,並且這個@Metadata註解是Kotlin獨有的,也就是Java是不會生成這樣的註解存在於.class檔案中,也就是從另一方面可以通過反射可以得知這個類是不是Kotlin的class.
3、@Metadata註解原始碼中每個引數的含義
@Metadata註解原始碼
package kotlin
/**
* This annotation is present on any class file produced by the Kotlin compiler and is read by the compiler and reflection.
* Parameters have very short names on purpose: these names appear in the generated class files, and we'd like to reduce their size.
*/
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
internal annotation class Metadata(
/**
* A kind of the metadata this annotation encodes. Kotlin compiler recognizes the following kinds (see KotlinClassHeader.Kind):
*
* 1 Class
* 2 File
* 3 Synthetic class
* 4 Multi-file class facade
* 5 Multi-file class part
*
* The class file with a kind not listed here is treated as a non-Kotlin file.
*/
val k: Int = 1,
/**
* The version of the metadata provided in the arguments of this annotation.
*/
val mv: IntArray = intArrayOf(),
/**
* The version of the bytecode interface (naming conventions, signatures) of the class file annotated with this annotation.
*/
val bv: IntArray = intArrayOf(),
/**
* Metadata in a custom format. The format may be different (or even absent) for different kinds.
*/
val d1: Array<String> = arrayOf(),
/**
* An addition to [d1]: array of strings which occur in metadata, written in plain text so that strings already present
* in the constant pool are reused. These strings may be then indexed in the metadata by an integer index in this array.
*/
val d2: Array<String> = arrayOf(),
/**
* An extra string. For a multi-file part class, internal name of the facade class.
*/
val xs: String = "",
/**
* Fully qualified name of the package this class is located in, from Kotlin's point of view, or empty string if this name
* does not differ from the JVM's package FQ name. These names can be different in case the [JvmPackageName] annotation is used.
* Note that this information is also stored in the corresponding module's `.kotlin_module` file.
*/
val pn: String = "",
/**
* An extra int. Bits of this number represent the following flags:
*
* 0 - this is a multi-file class facade or part, compiled with `-Xmultifile-parts-inherit`.
* 1 - this class file is compiled by a pre-release version of Kotlin and is not visible to release versions.
* 2 - this class file is a compiled Kotlin script source file (.kts).
*/
@SinceKotlin("1.1")
val xi: Int = 0
)
複製程式碼
注意: @Metadata註解中的k,mv,d1,d2..都是簡寫,為什麼要這樣做呢?說白了就是為了class檔案的大小,儘可能做到精簡。
引數簡寫名稱 | 引數全稱 | 引數型別 | 引數取值 | 引數含義 |
---|---|---|---|---|
k | kind | Int | 1: class,表示這個kotlin檔案是一個類或者介面 2: file,表示這個kotin檔案是一個.kt結尾的檔案 3: Synthetic class,表示這個kotlin檔案是一個合成類 4(Multi-file class facade) 5(Multi-file class part) |
表示當前metadata註解編碼種類 |
mv | metadata version | IntArray | - | metadata版本號 |
bv | bytecode version | IntArray | - | 位元組碼版本號 |
d1 | data1 | Array<String> | - | 主要記錄Kotlin語法資訊 |
d2 | data2 | Array<String> | - | 主要記錄Kotlin語法資訊 |
xs | extra String | String | - | 主要是為多檔案的類(Multi-file class)預留的名稱 |
xi | extra Int | Int | 0 (表示一個多檔案的類Multi-file class facade或者多檔案類的部分Multi-file class part編譯成-Xmultifile-parts-inherit) 1 (表示此類檔案由Kotlin的預發行版本編譯,並且對於發行版本不可見) 2 (表示這個類檔案是一個編譯的Kotlin指令碼原始檔) |
- |
pn | fully qualified name of package | String | - | 主要記錄kotlin類完整的包名 |
4、例項分析@Metadata註解
kotlin原始碼,定義的是一個.kt結尾檔案
package com.mikyou.kotlin.lambda.simple
typealias Sum = (Int, Int, Int) -> Int//typealias關鍵字宣告lambda表示式型別別名
fun main(args: Array<String>) {
val sum: Sum = { a, b, c ->//定義一個很簡單的三個數求和的lambda表示式
a + b + c
}
println(sum.invoke(1, 2, 3))
}
複製程式碼
反編譯後Java程式碼
package com.mikyou.kotlin.lambda.simple;
import kotlin.Metadata;
import kotlin.jvm.functions.Function3;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 10},
bv = {1, 0, 2},
k = 2,
d1 = {"\u0000\u001e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0010\b\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005*:\u0010\u0006\"\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u00072\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u0007¨\u0006\t"},
d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "Sum", "Lkotlin/Function3;", "", "production sources for module Lambda_main"}
)
public final class SumLambdaKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
Function3 sum = (Function3)SumLambdaKt$main$sum$1.INSTANCE;
int var2 = ((Number)sum.invoke(1, 2, 3)).intValue();
System.out.println(var2);
}
}
複製程式碼
- k = 2 表示的是這是一個.kt結尾的kotlin檔案
- mv = {1,1,10} 表示metadata版本號是1.1.10
- bv = {1,0,2} 表示bytecode版本號是1.0.2
- d1 = {...} 裡面的資訊是經過了 protobuf 編碼的二進位制流 具體可見詳細描述
- d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "Sum", "Lkotlin/Function3;", "", "production sources for module Lambda_main"}表示記錄有main函式,已經main函式引數名稱args,Sum就是通過typealias取lambda表示式型別別名,Lkotlin/Function3宣告的是Lambda表示式中生成的這個類是實現了FunctionN中Function3介面,因為它對應只有三個引數
kotlin中定義一個類原始碼
package com.mikyou.kotlin.lambda
/**
* Created by mikyou on 2018/3/27.
*/
data class Person(val name: String, val age: Int)
複製程式碼
反編譯後Java程式碼
package com.mikyou.kotlin.lambda;
import kotlin.Metadata;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 10},
bv = {1, 0, 2},
k = 1,//kind就是變成了1,表示這是一個kotlin的class類
d1 = {"\u0000 \n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\u000e\n\u0000\n\u0002\u0010\b\n\u0002\b\t\n\u0002\u0010\u000b\n\u0002\b\u0004\b\u0086\b\u0018\u00002\u00020\u0001B\u0015\u0012\u0006\u0010\u0002\u001a\u00020\u0003\u0012\u0006\u0010\u0004\u001a\u00020\u0005¢\u0006\u0002\u0010\u0006J\t\u0010\u000b\u001a\u00020\u0003HÆ\u0003J\t\u0010\f\u001a\u00020\u0005HÆ\u0003J\u001d\u0010\r\u001a\u00020\u00002\b\b\u0002\u0010\u0002\u001a\u00020\u00032\b\b\u0002\u0010\u0004\u001a\u00020\u0005HÆ\u0001J\u0013\u0010\u000e\u001a\u00020\u000f2\b\u0010\u0010\u001a\u0004\u0018\u00010\u0001HÖ\u0003J\t\u0010\u0011\u001a\u00020\u0005HÖ\u0001J\t\u0010\u0012\u001a\u00020\u0003HÖ\u0001R\u0011\u0010\u0004\u001a\u00020\u0005¢\u0006\b\n\u0000\u001a\u0004\b\u0007\u0010\bR\u0011\u0010\u0002\u001a\u00020\u0003¢\u0006\b\n\u0000\u001a\u0004\b\t\u0010\n¨\u0006\u0013"},
d2 = {"Lcom/mikyou/kotlin/lambda/Person;", "", "name", "", "age", "", "(Ljava/lang/String;I)V", "getAge", "()I", "getName", "()Ljava/lang/String;", "component1", "component2", "copy", "equals", "", "other", "hashCode", "toString", "production sources for module Lambda_main"}
)
public final class Person {
@NotNull
private final String name;
private final int age;
@NotNull
public final String getName() {
return this.name;
}
public final int getAge() {
return this.age;
}
public Person(@NotNull String name, int age) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
this.age = age;
}
@NotNull
public final String component1() {
return this.name;
}
public final int component2() {
return this.age;
}
@NotNull
public final Person copy(@NotNull String name, int age) {
Intrinsics.checkParameterIsNotNull(name, "name");
return new Person(name, age);
}
// $FF: synthetic method
@NotNull
public static Person copy$default(Person var0, String var1, int var2, int var3, Object var4) {
if ((var3 & 1) != 0) {
var1 = var0.name;
}
if ((var3 & 2) != 0) {
var2 = var0.age;
}
return var0.copy(var1, var2);
}
public String toString() {
return "Person(name=" + this.name + ", age=" + this.age + ")";
}
public int hashCode() {
return (this.name != null ? this.name.hashCode() : 0) * 31 + this.age;
}
public boolean equals(Object var1) {
if (this != var1) {
if (var1 instanceof Person) {
Person var2 = (Person)var1;
if (Intrinsics.areEqual(this.name, var2.name) && this.age == var2.age) {
return true;
}
}
return false;
} else {
return true;
}
}
}
複製程式碼
5、@Metadata註解需要注意的問題
我們知道了@Metadata註解是會一直保留至執行時,而且在配置混淆一定需要注意,@Metadata註解儲存的資訊是會被proguard給幹掉的,所以不能讓proguard幹掉,否則一些重要的資訊就會丟失,那麼這個.class檔案就是無效的,run在JVM上是會拋異常的。
五、如何去分析lambda表示式位元組碼
- 1、從Kotlin原始碼出發定義了一個三個數求和lambda表示式最後通過invoke()方法傳入三個引數完成lambda表示式的呼叫。
package com.mikyou.kotlin.lambda.simple
typealias Sum = (Int, Int, Int) -> Int //給型別取個別名
fun main(args: Array<String>) {
val sum: Sum = { a, b, c ->//定義一個很簡單的三個數求和的lambda表示式
a + b + c
}
println(sum.invoke(1, 2, 3))//invoke方法實現lambda表示式的呼叫
}
複製程式碼
- 2、然後再重點介紹幾個類和介面。
Lambda抽象類:
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlin.jvm.internal
public abstract class Lambda(private val arity: Int) : FunctionBase {
override fun getArity() = arity//實現FunctionBase介面中的抽象方法getArity(),並通過構造器把元數傳給它
override fun toString() = Reflection.renderLambdaToString(this)//toString()方法重寫
}
複製程式碼
FunctionBase介面,繼承了Function<R>介面和Serializable序列化介面(這是一個Java介面):
/*
* Copyright 2010-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlin.jvm.internal;
import kotlin.Function;
import java.io.Serializable;
public interface FunctionBase extends Function, Serializable {
int getArity();//抽象方法getArity
}
複製程式碼
FunctionN系列的介面( 0 <= N <= 22)也繼承了Function<R>介面,也就是目前支援的lambda表示式型別接收引數的個數不能超過22個,是不是突然感覺kotlin居然還有這種操作。並且每個介面中都有一個invoke抽象方法,用於外部呼叫lambda表示式。
/*
* Copyright 2010-2018 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Auto-generated file. DO NOT EDIT!
package kotlin.jvm.functions
/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
/** Invokes the function. */
public operator fun invoke(): R
}
/** A function that takes 1 argument. */
public interface Function1<in P1, out R> : Function<R> {
/** Invokes the function with the specified argument. */
public operator fun invoke(p1: P1): R
}
/** A function that takes 2 arguments. */
public interface Function2<in P1, in P2, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2): R
}
/** A function that takes 3 arguments. */
public interface Function3<in P1, in P2, in P3, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3): R
}
/** A function that takes 4 arguments. */
public interface Function4<in P1, in P2, in P3, in P4, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4): R
}
/** A function that takes 5 arguments. */
public interface Function5<in P1, in P2, in P3, in P4, in P5, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5): R
}
/** A function that takes 6 arguments. */
public interface Function6<in P1, in P2, in P3, in P4, in P5, in P6, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6): R
}
/** A function that takes 7 arguments. */
public interface Function7<in P1, in P2, in P3, in P4, in P5, in P6, in P7, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7): R
}
/** A function that takes 8 arguments. */
public interface Function8<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8): R
}
/** A function that takes 9 arguments. */
public interface Function9<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9): R
}
/** A function that takes 10 arguments. */
public interface Function10<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10): R
}
/** A function that takes 11 arguments. */
public interface Function11<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11): R
}
/** A function that takes 12 arguments. */
public interface Function12<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12): R
}
/** A function that takes 13 arguments. */
public interface Function13<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13): R
}
/** A function that takes 14 arguments. */
public interface Function14<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14): R
}
/** A function that takes 15 arguments. */
public interface Function15<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15): R
}
/** A function that takes 16 arguments. */
public interface Function16<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16): R
}
/** A function that takes 17 arguments. */
public interface Function17<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17): R
}
/** A function that takes 18 arguments. */
public interface Function18<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18): R
}
/** A function that takes 19 arguments. */
public interface Function19<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19): R
}
/** A function that takes 20 arguments. */
public interface Function20<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20): R
}
/** A function that takes 21 arguments. */
public interface Function21<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21): R
}
/** A function that takes 22 arguments. */
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
/** Invokes the function with the specified arguments. */
public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}
複製程式碼
Function<R>介面,我們都知道在kotlin中是可以把函式當做值來看待的,那麼這個函式值也是有型別的,也就是函式型別,這個Function就是lambda、匿名函式、普通命名函式的函式引用型別。
/*
* Copyright 2010-2015 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlin
/**
* Represents a value of a functional type, such as a lambda, an anonymous function or a function reference.
*
* @param R return type of the function.
*/
public interface Function<out R>
複製程式碼
lambda編譯後生成的class,通過生成位元組碼中的類可以看出, 生成唯一的類名是這個lambda表示式宣告處於哪個頂層檔案(它生成規則之前部落格有提到),哪個方法中,以及最終lambda表示式的名字組成, SumLambdaKt$main$sum$1, SumLambdaKt頂層檔名,在main函式宣告的,lambda表示式名為sum,由於Kotlin中的lambda只有三個引數,那麼這個Lambda類的arity元數也就是3,可以看到生成的類中構造器super(3)。然後就去實現了對應FunctionN介面也就是N=3,實現了Function3介面,重寫了invoke方法。
package com.mikyou.kotlin.lambda.simple;
import kotlin.jvm.internal.Lambda;
@kotlin.Metadata(mv = {1, 1, 10}, bv = {1, 0, 2}, k = 3, d1 = {"\000\n\n\000\n\002\020\b\n\002\b\004\020\000\032\0020\0012\006\020\002\032\0020\0012\006\020\003\032\0020\0012\006\020\004\032\0020\001H\n¢\006\002\b\005"}, d2 = {"<anonymous>", "", "a", "b", "c", "invoke"})
final class SumLambdaKt$main$sum$1 extends Lambda implements kotlin.jvm.functions.Function3<Integer, Integer, Integer, Integer> {
public final int invoke(int a, int b, int c) {
return a + b + c;
}
public static final SumLambdaKt$main$sum$1 INSTANCE =new SumLambdaKt$main$sum$1();//靜態SumLambdaKt$main$sum$1的例項INSTANCE供外部呼叫
SumLambdaKt$main$sum$1() {
super(3);//這個super傳入3,也就是前面getArity獲得引數的元數和函式引數個數一致
}
}
複製程式碼
為了清晰表達類與類之間關係請看下面這張類圖
呼叫處反編譯的Java程式碼
package com.mikyou.kotlin.lambda.simple;
import kotlin.Metadata;
import kotlin.jvm.functions.Function3;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 10},
bv = {1, 0, 2},
k = 2,
d1 = {"\u0000\u001e\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0010\b\n\u0000\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005*:\u0010\u0006\"\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u00072\u001a\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u0007¨\u0006\t"},
d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "Sum", "Lkotlin/Function3;", "", "production sources for module Lambda_main"}
)
public final class SumLambdaKt {
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
Function3 sum = (Function3)SumLambdaKt$main$sum$1.INSTANCE;//呼叫靜態SumLambdaKt$main$sum$1的例項INSTANCE,強轉成Function3型別。
int var2 = ((Number)sum.invoke(1, 2, 3)).intValue();//然後用這個sum物件例項去呼叫Function3中三個引數的invoke方法
System.out.println(var2);
}
}
複製程式碼
最後我們再梳理一下整個編譯的流程:
首先,定義好的Kotlin Lambda表示式,通過Lambda表示式的型別,可以得到引數的個數以及引數的型別,也就是向Lambda抽象類的構造器傳遞arity元數,Lambda抽象類又把arity傳遞給FunctionBase,在編譯時期會根據這個arity元數動態確定需要實現FunctionN介面,然後通過實現了相應的FunctionN介面中的invoke方法,最後lambda表示式函式體內程式碼邏輯將會在invoke方法體內。整個編譯的流程完畢,也就在本地目錄會生成一個.class位元組碼檔案。從呼叫處反編譯的程式碼就會直接呼叫.class位元組碼中已經生成的類中的INSTANCE靜態例項物件,最後通過這個例項去呼叫invoke方法。
六、使用lambda表示式時的效能優化
我們都知道lambda表示式可以作為一個引數傳入到另一個函式中,這個也稱為高階函式。在使用高階函式的時候我們需要注意儘量把我們的高階函式宣告成inline行內函數,因為通過上面的位元組碼分析的編譯過程知道lambda最終會被編譯成一個FunctionN類,然後呼叫的地方是使用這個FunctionN的例項去呼叫相應invoke方法。如果宣告成行內函數的話,那麼將不會去生成這個類並且在函式呼叫處是不需要例項化這個FunctionN的例項,而是在呼叫的時把呼叫的方法給替換上去,可以降低很大的效能開銷。一起來看個例子
沒有設定inline函式的case:
package com.mikyou.kotlin.lambda.high
typealias SumAlias = (Int, Int) -> Int
fun printSum(a: Int, b: Int, block: SumAlias) {//沒有設定inline關鍵字
println(block.invoke(a, b))
}
fun main(args: Array<String>) {
printSum(4, 5) { a, b ->
a + b
}
}
複製程式碼
編譯生成.clas檔案目錄,一個是呼叫處生成的位元組碼,另一處則是宣告Lambda表示式位元組碼:
函式呼叫處生成的位元組碼
package com.mikyou.kotlin.lambda.high;
import kotlin.Metadata;
import kotlin.jvm.functions.Function2;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 10},
bv = {1, 0, 2},
k = 2,
d1 = {"\u0000(\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0010\b\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005\u001a4\u0010\u0006\u001a\u00020\u00012\u0006\u0010\u0007\u001a\u00020\b2\u0006\u0010\t\u001a\u00020\b2\u001c\u0010\n\u001a\u0018\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000bj\u0002`\f*.\u0010\r\"\u0014\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000b2\u0014\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000b¨\u0006\u000e"},
d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "printSum", "a", "", "b", "block", "Lkotlin/Function2;", "Lcom/mikyou/kotlin/lambda/high/SumAlias;", "SumAlias", "production sources for module Lambda_main"}
)
public final class SumHighFuntionKt {
public static final void printSum(int a, int b, @NotNull Function2 block) {
Intrinsics.checkParameterIsNotNull(block, "block");
int var3 = ((Number)block.invoke(a, b)).intValue();
System.out.println(var3);
}
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
printSum(4, 5, (Function2)null.INSTANCE);//傳入了Function2型別INSTANCE例項到printSum函式中
}
}
複製程式碼
lambda宣告處反編譯的程式碼
package com.mikyou.kotlin.lambda.high;
import kotlin.jvm.internal.Lambda;
@kotlin.Metadata(mv = {1, 1, 10}, bv = {1, 0, 2}, k = 3, d1 = {"\000\n\n\000\n\002\020\b\n\002\b\003\020\000\032\0020\0012\006\020\002\032\0020\0012\006\020\003\032\0020\001H\n¢\006\002\b\004"}, d2 = {"<anonymous>", "", "a", "b", "invoke"})
final class SumHighFuntionKt$main$1 extends Lambda implements kotlin.jvm.functions.Function2<Integer, Integer, Integer> {
public static final 1INSTANCE =new 1();
public final int invoke(int a, int b) {
return a + b;
}
SumHighFuntionKt$main$1() {
super(2);
}
}
複製程式碼
設定inline函式的case:
package com.mikyou.kotlin.lambda.high
typealias SumAlias = (Int, Int) -> Int
inline fun printSum(a: Int, b: Int, block: SumAlias) {//設定inline,printSum為行內函數
println(block.invoke(a, b))
}
fun main(args: Array<String>) {
printSum(4, 5) { a, b ->
a + b
}
}
複製程式碼
編譯生成.clas檔案目錄,只有一個呼叫處生成的位元組碼:
函式呼叫處生成的位元組碼
package com.mikyou.kotlin.lambda.high;
import kotlin.Metadata;
import kotlin.jvm.functions.Function2;
import kotlin.jvm.internal.Intrinsics;
import org.jetbrains.annotations.NotNull;
@Metadata(
mv = {1, 1, 10},
bv = {1, 0, 2},
k = 2,
d1 = {"\u0000(\n\u0000\n\u0002\u0010\u0002\n\u0000\n\u0002\u0010\u0011\n\u0002\u0010\u000e\n\u0002\b\u0003\n\u0002\u0010\b\n\u0002\b\u0002\n\u0002\u0018\u0002\n\u0002\u0018\u0002\n\u0002\b\u0002\u001a\u0019\u0010\u0000\u001a\u00020\u00012\f\u0010\u0002\u001a\b\u0012\u0004\u0012\u00020\u00040\u0003¢\u0006\u0002\u0010\u0005\u001a7\u0010\u0006\u001a\u00020\u00012\u0006\u0010\u0007\u001a\u00020\b2\u0006\u0010\t\u001a\u00020\b2\u001c\u0010\n\u001a\u0018\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000bj\u0002`\fH\u0086\b*.\u0010\r\"\u0014\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000b2\u0014\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b\u0012\u0004\u0012\u00020\b0\u000b¨\u0006\u000e"},
d2 = {"main", "", "args", "", "", "([Ljava/lang/String;)V", "printSum", "a", "", "b", "block", "Lkotlin/Function2;", "Lcom/mikyou/kotlin/lambda/high/SumAlias;", "SumAlias", "production sources for module Lambda_main"}
)
public final class SumHighFuntionKt {
public static final void printSum(int a, int b, @NotNull Function2 block) {
Intrinsics.checkParameterIsNotNull(block, "block");
int var4 = ((Number)block.invoke(a, b)).intValue();
System.out.println(var4);
}
public static final void main(@NotNull String[] args) {
Intrinsics.checkParameterIsNotNull(args, "args");
//下面呼叫地方直接是把傳入block替換到該呼叫處執行,根本就不需要了Function2例項物件,這樣會降低類建立和生成的開銷。
byte a$iv = 4;
int b$iv = 5;
int var4 = a$iv + b$iv;
System.out.println(var4);
}
}
複製程式碼
七、使用lambda表示式需要注意哪些問題
- 1、在使用proguard的時候需要注意不要將@Metadata註解中資訊給混淆了,否則會有異常丟擲。
- 2、在使用高階函式時,儘量去使用inline函式,降低類生成和類的例項建立的開銷。關於行內函數,我們會在接下來部落格一一詳細介紹。
歡迎關注Kotlin開發者聯盟,這裡有最新Kotlin技術文章,每週會不定期翻譯一篇Kotlin國外技術文章。如果你也喜歡Kotlin,歡迎加入我們~~~