本文基于Kotlin 1.7.0版别,现在Kotlin最新版别为1.8.22

信任大家在日常运用Kotlin的进程中,现已触摸了许多inline函数,包括源码中也有许多许多办法运用了inline来修改某些办法,不知道是不是有种疑问,一个办法明明能够直接调用,为啥非要用inline来润饰呢?inline修改的办法参数中,居然还有noinlinecrossinline关键字来润饰lambda。下面来详细说明下这三个关键字的作用和运用场景。

inline内联

inline润饰的办法叫做内联函数,它润饰的办法需求接收了一个或多个lambda表达式作为参数,

假如此办法参数没有lambda表达式,那么编译器将提醒你Expected performance impact from inlining is insignificant. Inlining works best for functions with parameters of functional types,这个正告说明:此内联对功能的影响很小、微乎其微,内联适合的是具有函数类型的参数,所以编译器觉得此办法不适用于inline润饰。

咱们来经过inline和非inline函数对比一下它的作用,两个办法完成相同的功用,然后看下最终编译器是如何调用两种函数。

// 此函数为内联函数,传入一个lambda表达式
inline fun inlineTest(block: () -> Unit) {
    println("inlineTest")
    block()
}
// 此函数为非内联函数,也是传入一个lambda表达式
fun test(block: () -> Unit) {
    println("test")
    block()
}

两个函数除了inline关键字润饰不同意外,其他都是共同的,接下来看看调用之后的输出是否共同,此处咱们加上了测量函数履行时间的功用,以此更直观的观察inline带来的作用。

fun main() {
    println(measureTimeMillis {
        inlineTest {
            println("main inlineTest")
        }
    })
    println(measureTimeMillis {
        test {
            println("main test")
        }
    })
}
# 
inlineTest
main inlineTest
1
test
main test
9
inline函数履行时间为1,非inline函数履行时间为9

两个函数履行的作用是共同的,但是从输出日志中能够暗处,inline函数的履行时间要显着比非inline函数少,这也便是为什么官方推荐咱们在有lambda参数的时候加上inline使其变为内联函数,那么inline究竟有了什么魔方能够削减了函数调用的开支呢,下面经过将class反编译成Java来看看函数的详细调用。

public final class MainKt {
   public static final void main() {
    	// ①此处为inline函数的调用进程
      long start$iv = System.currentTimeMillis();
      System.out.println("inlineTest");
      System.out.println("main inlineTest");
      long var6 = System.currentTimeMillis() - start$iv;
      System.out.println(var6);
    	// ②此处为非inline函数的调用进程
      start$iv = System.currentTimeMillis();
      test((Function0)null.INSTANCE);
      var6 = System.currentTimeMillis() - start$iv;
      System.out.println(var6);
   }
   // ③在编译器中能够看到此处inline函数并没有调用的当地
   public static final void inlineTest(@NotNull Function0 block) {
      Intrinsics.checkNotNullParameter(block, "block");
      int $i$f$inlineTest = false;
      System.out.println("inlineTest");
      block.invoke();
   }
   public static final void test(@NotNull Function0 block) {
      Intrinsics.checkNotNullParameter(block, "block");
      System.out.println("test");
      block.invoke();
   }
   // $FF: synthetic method
   public static void main(String[] args) {
      main();
   }
}

在反编译的代码中标记了三处当地,顺着这三处就能够清晰的看出inline函数和常规函数的不同之处。

榜首处①是inline函数的详细调用进程,从代码中能够看出,此处并没有直接调用inlineTest()这个办法,反而是直接将函数的内容拷贝到调用途,并且将lambda中的代码也一并拷贝过来了,直接削减了函数调用的开支;

第二处②是常规函数的调用进程,它是调用了test()办法,并且传入了一个Function目标,这个Function0便是咱们lambda表达式

第三处③是需求在编译器中才干看出作用,在编译器中,咱们能够看出teinlineTest()函数并没有调用者,而test()函数是在main()函数中有调用的当地。

这样咱们就能够直观的感触到,inline润饰的函数也便是内联函数在调用的时候并非直接调用此函数自身,而是将函数内的代码直接拷贝到调用途。这样带来优势便是:削减函数调用带来的开支,进步程序的功能;消除lambda表达式带来的额定开支,避免创建额定的目标。

noinline

上面咱们了解了inline内联函数的运用和优势,接着咱们看一下合作inline运用的noinline,看名字大致能够猜测到,noinline便是非内联的意思,也便是表明被noinline润饰的参数强制不允许内联,此参数作为一个普通的函数引用传递,并且noinline有必要搭配inline运用。下面仍是经过代码来直观感触下noinline的作用。

fun main() {
    noinlineTest({
        println("main inline")
    }, {
        println("main noInline")
    })
}
// 界说一个内联函数,榜首个参数可内联运用,第二个参数运用noinline润饰,强制不内联
inline fun noinlineTest(block: () -> Unit, noinline no: () -> Unit) {
    println("noinlineTest")
    block()
    no()
}
# log
noinlineTest
main inline
main noInline

noinlineTest({},{})函数为一个内联函数,两个lambda参数唯一不同的便是第二个参数被noinline润饰了,从log中能够看出,输出的信息在咱们意料之中,也并不能看出noinline带来的不同之处,咱们仍是得反编译看下生成的代码究竟变化了什么。

public final class NoinlineKt {
   public static final void main() {
    	// ① no参数为一个Function,直接实例化了
      Function0 no$iv = (Function0)null.INSTANCE;
    	// ② noinlineTest函数中打印的日志
      System.out.println("noinlineTest");
    	// ③ block函数内代码直接拷贝到这
      System.out.println("main inline");
      // ④ 履行no参数详细的代码
      no$iv.invoke();
   }
   public static final void noinlineTest(@NotNull Function0 block, @NotNull Function0 no) {
      Intrinsics.checkNotNullParameter(block, "block");
      Intrinsics.checkNotNullParameter(no, "no");
      int $i$f$noinlineTest = false;
      System.out.println("noinlineTest");
      block.invoke();
      no.invoke();
   }
   // $FF: synthetic method
   public static void main(String[] args) {
      main();
   }
}

反编译的代码中咱们注释了四处当地,分别介绍了内联参数和noinline参数的履行过程:

榜首处①先实例化一个no参数,Kotlin的lambdaJava中对应的是Function目标;

第二处②直接拷贝内联函数noinlineTest()println()办法输出日志;

第三处③直接拷贝内联参数的输出日志办法,从此处能够看到block参数被内联了,它被拷贝到调用途;

第四处④履行了noinline参数的内部代码

从上面反编译的代码咱们能够得出,noinline润饰的参数被强制非内联了,它仍是会去调用内部的代码,而非直接拷贝内部代码到调用途,这便是noinline关键字的作用。

crossinline

crossinline相对于前面inlinenoinline来说,它运用的当地较少,个人的了解它的意思为强制内联的意思,它表明被润饰的lambda参数强制履行内联作用,一般咱们见到的运用它的当地都是在内联函数中运用了lambda表达式,并且在此表达式调用了内联函数的lambda参数,此刻假如不运用crossinline润饰参数,编译器会报错,下面咱们经过代码来说明

inline fun crossinlineTest(block: () -> Unit, crossinline cross: () -> Unit) {
    println("crossinlineTest")
    thread {
      	// 编译器会在此处报错:Can't inline 'block' here: it may contain non-local returns. Add 'crossinline' modifier to parameter declaration 'block'
        block()
        cross()
    }
}

上面便是一个内联函数运用crossinline的实例,咱们在内联函数中运用thread{}开启一个线程,并在新的线程中调用block()cross()参数的履行,此刻编译器在block()调用途就直接报错,告知咱们block在此种场景下不能直接内联,它有可能包含了非本地的return,这样咱们就需求加上crossinline来润饰参数,从cross调用的状况就说明了它能够正常履行。

编译器为什么不允许咱们在thread{}中直接履行block()参数呢?

由于内联函数在调用的时候是直接将代码拷贝到调用途的,所以存在block()中直接return的状况,他会履行回来到调用途并且不再履行调用途后续的代码,看个详细代码了解一下:

inline fun inlineReturn(block: () -> Unit) {
    block()
}
fun main() {
    inlineReturn {
        println("start")
        return
        // 此处将不会履行
        println("end")
    }
  	// 此处也不会履行
    println("main")
}
# log
start

看上面代码,咱们直接在inlineReturn函数的block()中运用return来回来,成果它并非退出到内联函数,而是直接退出了main()函数,到这咱们记住inline函数是能够直接运用return来做出回来操作。

下面咱们再看看crossinline关键字的作用:

inline fun crossinlineReturn(crossinline block: () -> Unit) {
    block()
}
fun main() {
    crossinlineReturn {
        println("start")
      	// 此处编译器会直接报错
        return
        println("end")
    }
    println("main")
}

crossinlineReturn和上面inlineReturn函数基本共同,仅仅block参数运用了crossinline润饰,此刻咱们假如还想运用return来操作回来,编译器会直接给出报错提示,告知咱们此刻不能够运用return,它需求指定回来的目的地,需求选用return@crossinlineReturn这样的形式,告知编译器仅仅退出到内联函数,并非直接退出main()函数,并且它的输出为:

# log
start
main

经过crossinline就能够禁止在内联的lambda表达式中运用return操作了。

好了,到这为止咱们现已将inlinenoinlinecrossinline三者的关系及其用法、作用都介绍完了,假如你有收获帮助点个关注吧,欢迎谈论区输出不同的看法和认为不正确的当地,ths!