kotlin协程框架涉及很多关键字和语法糖,其中高阶函数、内联函数被多处使用
1、高阶函数
高阶函数其实就是一个函数(或者方法),将这个函数(或者方法)作为一个参数值传入到另一个函数里并且采用lambda表达式特性进行缩写,函数可以是有无传参或返回值,高阶函数也是
例如定义一个打印log高阶函数
例 1
private fun logd(msg: () -> String){
Log.d("LOG", ">>> ${msg()}" )
}
复制代码
其中参数 msg: () -> String
为声明一个高阶函数,msg
为这个高阶函数的命名(可以随意);()
为这个高阶函数的传参声明,当前为无参,有参数可以写成(a: String, b: Int)
或者(String, Int)
; -> String
为这个高阶函数的返回值,箭头指向表示高阶函数的返回对象类型,当前为返回字符串类型,无返回值可以写成 -> Unit
(Unit源码解释: The type with only one value: the Unit
object. This type corresponds to the void
type in Java. 意思是类似java中的void
)
打印log高阶函数调用:
logd { "测试" }
复制代码
调用高阶函数采用lambda表达式进行缩写,kotlin中有约定:当函数只有一个传参并且参数是高阶函数时可以省略()进行缩写;当函数不止一个传参但是最后一个传参是高阶函数时,可以在()外指定
例 2
//定义
private fun logd(tag: String, msg: () -> String){
Log.d(tag, ">>> ${msg()}" )
}
//调用
logd("LOG"){
"测试"
}
复制代码
总结
-
将函数(或者方法)作为传参并不稀奇, java中可以传有返回值的函数作为参数,但是java会优先执行这个传参函数,而kotlin中的高阶函数不同,这样传参并不会立刻执行,只有调用了才会执行传的函数,这样可以做很多事,比如将这个函数挂载到子线程执行等等,kotlin协程框架就是按照这个语言特性设计的,kotlin协程框架可以说是语言特性造就的一个框架
-
缺陷就是高阶函数会被编译成匿名接口类,每调用一次会创建一次,类的开销很大,需要采用内联函数来解决(生成匿名类的举例在内联函数中会详细介绍)
协程框架很多地方使用了高阶函数,其中有:launch
函数与async
函数:
//launch函数
public fun CoroutineScope.launch(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> Unit
): Job {
···
}
//async函数
public fun <T> CoroutineScope.async(
context: CoroutineContext = EmptyCoroutineContext,
start: CoroutineStart = CoroutineStart.DEFAULT,
block: suspend CoroutineScope.() -> T
): Deferred<T> {
···
}
复制代码
将函数挂载到一个协程里执行
2、内联函数
通过inline关键字修饰的函数方法称之为内联函数
复制代码
内联函数的作用
(1)、防止高阶函数反编译为匿名类,减少类创建开销
例 3
class InlineTest {
fun test(){
logd {
"测试"
}
logi {
"测试"
}
}
private fun logd(msg: () -> String){
Log.d("LOG", ">>> ${msg()}" )
}
inline fun logi(msg: () -> String){
Log.i("LOG", ">>> ${msg()}" )
}
}
复制代码
反编译结果:
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\u001e\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n\u0002\u0010\u000e\n\u0002\b\u0003\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\u0016\u0010\u0003\u001a\u00020\u00042\f\u0010\u0005\u001a\b\u0012\u0004\u0012\u00020\u00070\u0006H\u0002J\u0017\u0010\b\u001a\u00020\u00042\f\u0010\u0005\u001a\b\u0012\u0004\u0012\u00020\u00070\u0006H\u0086\bJ\u0006\u0010\t\u001a\u00020\u0004¨\u0006\n"},
d2 = {"Lcom/wxk/coroutines/InlineTest;", "", "()V", "logd", "", "msg", "Lkotlin/Function0;", "", "logi", "test", "app"}
)
public final class InlineTest {
public final void test() {
this.logd((Function0)null.INSTANCE);
int $i$f$logi = false;
StringBuilder var5 = (new StringBuilder()).append(">>> ");
String var4 = "LOG";
int var3 = false;
String var6 = "测试";
Log.i(var4, var5.append(var6).toString());
}
private final void logd(Function0 msg) {
Log.d("LOG", ">>> " + (String)msg.invoke());
}
public final void logi(@NotNull Function0 msg) {
int $i$f$logi = 0;
Intrinsics.checkParameterIsNotNull(msg, "msg");
Log.i("LOG", ">>> " + (String)msg.invoke());
}
}
复制代码
匿名接口Function0:
/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
/** Invokes the function. */
public operator fun invoke(): R
}
复制代码
反编译的代码比较明显,logd()
方法没有被inline
关键字修饰,在反编译后会有创建一个匿名接口类Function0
;被inline
关键字修饰的logi()
方法则是相当于拷贝了一份代码到test()
方法中,没有而外的创建匿名类
(2)、杜绝频繁调用相同方法造成方法进栈出栈所带来的性能损耗
例 4
class InlineTest {
fun test(){
for (i in 1..3){
logd()
}
for (i in 1..3){
logi()
}
}
private fun logd(){
Log.d("LOG", ">>> logd" )
}
inline fun logi(){
Log.i("LOG", ">>> logi" )
}
}
复制代码
反编译结果:
@Metadata(
mv = {1, 1, 16},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\b\u0003\u0018\u00002\u00020\u0001B\u0005¢\u0006\u0002\u0010\u0002J\b\u0010\u0003\u001a\u00020\u0004H\u0002J\t\u0010\u0005\u001a\u00020\u0004H\u0086\bJ\u0006\u0010\u0006\u001a\u00020\u0004¨\u0006\u0007"},
d2 = {"Lcom/wxk/coroutines/InlineTest;", "", "()V", "logd", "", "logi", "test", "app"}
)
public final class InlineTest {
public final void test() {
int var1 = 1;
byte var2;
for(var2 = 3; var1 <= var2; ++var1) {
this.logd();
}
var1 = 1;
for(var2 = 3; var1 <= var2; ++var1) {
int $i$f$logi = false;
Log.i("LOG", ">>> logi");
}
}
private final void logd() {
Log.d("LOG", ">>> logd");
}
public final void logi() {
int $i$f$logi = 0;
Log.i("LOG", ">>> logi");
}
}
复制代码
通过反编译结果可以看出, logd()
方法没有被inline
关键字修饰,在循环调用时会逐个调用logd()
方法,会造成方法大量进栈出栈操作;被inline
关键字修饰的logi()
方法则是相当于拷贝了一份代码到循环体中,没有频繁调用logi()
方法
(3)、泛型对象的简易传输与泛型实体类型获取(不需要强转操作)
例 5
class InlineTest {
fun test(){
val json = "{\"age\":18,\"name\":\"kk\"}"
val generic = GenericTest().resolve<Generic>(json)
Log.d("LogUtils", "age == ${generic.age}")
Log.d("LogUtils", "name == ${generic.name}")
}
}
/**
* 定义内联函数
*/
class GenericTest{
inline fun <reified T> resolve(json: String): T {
return resolve(T::class.java, json)
}
fun <T> resolve(clazz: Class<T>, json: String) : T{
return Gson().fromJson(json, clazz) as T
}
}
/**
* 定义一个实体对象
*/
data class Generic(
val age: Int,
val name: String
)
复制代码
运行结果:
D/LogUtils: age == 18
D/LogUtils: name == kk
复制代码
通过 例5 的GenericTest
对象的内联操作, 泛型T
得到的是具体实体对象Generic
,不需要通过强转得到, GenericTest
对象也无需引用Generic
3、kotlin通用的invoke()函数
invoke()
方法是kotlin对象类中默认持有的方法,可以通过operator
关键字重载invoke()
方法
例 6
enum class OperatorTest {
TEST;
operator fun invoke(data: String){
Log.d("LogUtils","data : $data")
}
}
fun execute(){
val start = OperatorTest.TEST
//原始调用方式
start.invoke("测试1")
//简化调用方式
start("测试2")
}
复制代码
运行结果:
D/LogUtils: data : 测试1
D/LogUtils: data : 测试2
复制代码
kotlin类默认含有invoke()
方法,并且可以通过operator
关键字重载,可以采用原始调用方式:class.invoke(···)
;kotlin允许简易调用:class()
参考
2、关键字与操作符