Android开发Android开发经验谈

Kotlin中的inline关键字 - 内联函数

2021-05-31  本文已影响0人  盛世光阴

版权归作者所有,转发请注明出处:https://www.jianshu.com/p/14a837e67dd7

Kotlin中的inline关键字 - 内联函数
Kotlin中的inline关键字 - 内联类

前言

Kotlin 是一种在 Java 虚拟机上运行的静态类型编程语言,被称之为 Android 世界的Swift,在Google I/O 2017中,Google 宣布 Kotlin 成为 Android 官方开发语言

img.jpg

内联函数

inline关键字所修饰的函数为内联函数

内联函数的作用

尝试去修饰普通函数

inline fun getAmount() = 0.1

这种写法编辑器会有警告提示:
Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types
意味着使用inline只有在修饰一个参数为functional types的函数时候效果最好,也就是我们所说的高阶函数的一种,kotlin中的forEach函数中参数action就是一个函数类型,所以forEach函数使用了inline 修饰

@kotlin.internal.HidesMembers
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
    for (element in this) action(element)
}

inline修饰高阶函数的好处
我们对高阶函数分别使用普通函数声明,和使用内联函数声明

class Test {
    
    //不使用inline 修饰的高阶函数
    fun test(f: () -> Unit) {
        f()
    }

    //使用inline 修饰的高阶函数
    inline fun testInline(f: () -> Unit) {
        f()
    }

    fun call() {
        test {
            print("test")
        }
        testInline {
            print("testInline")
        }
    }
}

转化为Java代码

public final class Test {
   public final void test(@NotNull Function0 f) {
      Intrinsics.checkParameterIsNotNull(f, "f");
      f.invoke();
   }

   public final void testInline(@NotNull Function0 f) {
      int $i$f$testInline = 0;
      Intrinsics.checkParameterIsNotNull(f, "f");
      f.invoke();
   }

   public final void call() {
      this.test((Function0)null.INSTANCE);
      int $i$f$testInline = false;
      int var3 = false;
      String var4 = "testInline";
      boolean var5 = false;
      System.out.print(var4);
   }
}

call()中可以看出,第一行,当调用非内联函数时是直接调用了此函数,并且创建了匿名类Function0用于Lambda函数的调用

this.test((Function0)null.INSTANCE);

call()中的其他代码可以看出,内联函数则是复制了函数体过来,而没有创建匿名类,而是直接嵌入了Lambda函数的实现体

int $i$f$testInline = false;
int var3 = false;
String var4 = "testInline";
boolean var5 = false;
 System.out.print(var4);

当高阶函数没有使用Inline修饰时,调用此函数会直接引用此函数,并且会创建匿名类以实现此函数参数的调用,这有两部分开销,直接调用此函数会创建额外的栈帧以及入栈出栈操作(一个函数的调用就是一个栈帧入栈和出栈的过程),并且匿名类的创建也会消耗性能

使用 Inline 修饰高阶函数是会有性能上的提升

避免内联大型函数

在调用内联函数时,基于上述我们已经知道内联会将Lambda参数函数体直接进行嵌入,避免了函数的引用以及栈帧和对象创建的开销,但是如果Lambda所内联的函数体数量太大,嵌入则会造成调用位置的函数体增长,所以需要 避免内联大型函数

noinline关键字使用

内联Lambad只能在内联函数内部使用,或者作为内联参数进行传递,如果想 将内联Lambad作为普通函数参数进行传递或者存储在字段中则不能使用内联函数声明,或者在声明为内联函数然后将需要传递的Lambda参数指定为noinline

inline fun testInline(f: () -> Unit) {
    testInlineInner(f) //会提示错误,内联Lambad 不能作为普通函数参数进行传递
}

fun testInlineInner(f: () -> Unit) {
    f()
}

如果一个函数中有多个函数参数,并且部分参数时可以直接使用内联方式调用,另一部分则需要进行传递或者赋值,则可以将函数声明为内联函数,内联函数中,不符合内联方式的参数使用noinline修饰

inline fun testInline(f1: () -> Unit, noinline f: () -> Unit) {
    f1()
    testInlineInner(f)
}

fun testInlineInner(f: () -> Unit) {
    f()
}

内联Lambda中使用return

fun call() {
    testOrdinary {
        print("testOrdinary")
        return //报错
    }
    testOrdinary {
        print("testOrdinary")
        return@testOrdinary //正常 退出 testOrdinary 
    }
    testInline {
        print("testInline")
        return //正常 退出call()
    }
    testInline {
        print("testInline")
        return@testInline //正常 退出 testInline 
    }
}

crossinline关键字使用

当内联Lambda不是直接在函数体中调用,而是在嵌套函数或者其他执行环境中调用则需要声明为crossinline

inline fun testInline(crossinline callBack: () -> Unit) {
    object : Thread(){
        override fun run() {
            callBack() //执行在object中,需要将callBack声明为 crossinline
        }
    }
}

Reified 类型的参数

reified 是Kotlin关于范型的关键字,从而达到泛型不被擦除,如果需要使用reified 去修饰泛型方法中的泛型类型,则需要使用Inline修饰此泛型方法,因为Inline函数可以指定泛型类型不被擦除,因为内联函数编译期会将字节码嵌入到调用它的地方,所以编译器才会知道泛型对应的具体类型,使用reified 和 Inline适用于泛型方法,并且方法体中需要对泛型类型做以判断的情况

inline fun <reified T> Bundle.plus(key: String, value: T) {
    when (value) {
        is String -> putString(key, value)
        is Long -> putLong(key, value)
        is Int -> putInt(key, value)
    }
}

Inline properties

inline也可以修饰setget方法也会在调用位置进行嵌入代码以收益性能,并且可以直接修饰属性使两个访问器都标记为inline

class Amount(var amount: Long) {
    private val isEmpty: Boolean
        inline get() {
            return amount <= 0
        }

    private val isEmptyNoInline: Boolean
        get() {
            return amount <= 0
        }

    fun test(){
       val amount = Amount(10)
        val isEmpty = amount.isEmpty
        val isEmptyNoInline = amount.isEmptyNoInline
    }
    //转化为Java代码
    public final void test() { 
      Amount amount = new Amount(10L);
      int $i$f$isEmpty = false;
      boolean isEmpty = amount.getAmount() <= 0L; //代码嵌入
      boolean isEmptyNoInline = amount.isEmptyNoInline(); //非嵌入
   }
}

欢迎关注Mike的简书

Android 知识整理

上一篇下一篇

猜你喜欢

热点阅读