网站建设免费建站免费源代码,适合年轻人开的工作室,石狮新站seo,flash如何做网页主要内容#xff1a;
inline 高价函数的原理分析Non-local returns noinlinecrossinline
inline
如果有C语言基础的#xff0c;inline 修饰一个函数表示该函数是一个内联函数。编译时#xff0c;编译器会将内联函数的函数体拷贝到调用的地方。我们先看下在一个普通的 kot…主要内容
inline 高价函数的原理分析Non-local returns noinlinecrossinline
inline
如果有C语言基础的inline 修饰一个函数表示该函数是一个内联函数。编译时编译器会将内联函数的函数体拷贝到调用的地方。我们先看下在一个普通的 kotlin 函数上使用 inline 关键字
inline fun inlineFun() {println(from inlineFun)
}会发现 IDE 会给出警告 建议我们在高阶函数上使用 inline 关键字。
好那我们来看下高阶函数。
高价函数的原理分析
下面是一个简单的高阶函数函数参数是一个 function type 类型
private fun proxy(action: () - Unit) {println(start logging)action()println(end logging)
}编译后对应的 Java 代码为
private final void proxy(Function0 action) {String var2 start logging;System.out.println(var2);action();var2 end logging;System.out.println(var2);
}会将 function type 编译成 Function0 类型因为 action: () - Unit括号内是无参的所以是 Function0如果是一个参数对应 Function1以此类推。然后我们调用上面的高阶函数 proxy:
fun invokeProxy() {proxy {println(eating)}
}查看对应的字节码 public final invokeProxy()VL0LINENUMBER 34 L0ALOAD 0GETSTATIC inline/InlineTest$invokeProxy$1.INSTANCE : Linline/InlineTest$invokeProxy$1;CHECKCAST kotlin/jvm/functions/Function0INVOKESPECIAL inline/InlineTest.proxy (Lkotlin/jvm/functions/Function0;)VL1LINENUMBER 38 L1RETURNL2LOCALVARIABLE this Linline/InlineTest; L0 L2 0MAXSTACK 2MAXLOCALS 1可以看出编译后会生成一个内部类inline/InlineTest$invokeProxy$1然后我们看下这个内部类长什么样
final class inline/InlineTest$invokeProxy$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {// access flags 0x1041public synthetic bridge invoke()Ljava/lang/Object;L0LINENUMBER 15 L0ALOAD 0INVOKEVIRTUAL inline/InlineTest$invokeProxy$1.invoke ()VGETSTATIC kotlin/Unit.INSTANCE : Lkotlin/Unit;ARETURNMAXSTACK 1MAXLOCALS 1// access flags 0x11public final invoke()VL0LINENUMBER 36 L0LDC eatingASTORE 1L1GETSTATIC java/lang/System.out : Ljava/io/PrintStream;ALOAD 1INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)VL2L3LINENUMBER 37 L3RETURNL4LOCALVARIABLE this Linline/InlineTest$invokeProxy$1; L0 L4 0MAXSTACK 2MAXLOCALS 2// access flags 0x0init()VALOAD 0ICONST_0INVOKESPECIAL kotlin/jvm/internal/Lambda.init (I)VRETURNMAXSTACK 2MAXLOCALS 1// access flags 0x19public final static Linline/InlineTest$invokeProxy$1; INSTANCE// access flags 0x8static clinit()VNEW inline/InlineTest$invokeProxy$1DUPINVOKESPECIAL inline/InlineTest$invokeProxy$1.init ()VPUTSTATIC inline/InlineTest$invokeProxy$1.INSTANCE : Linline/InlineTest$invokeProxy$1;RETURNMAXSTACK 2MAXLOCALS 0Lkotlin/Metadata;(mv{1, 8, 0}, k3, d1{\u0000\u0008\n\u0000\n\u0002\u0010\u0002\n\u0000\u0010\u0000\u001a\u00020\u0001H\n\u00a2\u0006\u0002\u0008\u0002}, d2{anonymous, , invoke})OUTERCLASS inline/InlineTest invokeProxy ()V// access flags 0x18final static INNERCLASS inline/InlineTest$invokeProxy$1 null null// compiled from: InlineTest.kt
}上面的字节码类似下面的伪代码
final class InlineTest$invokeProxy$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {public final static InlineTest$invokeProxy$1 INSTANCE;static {INSTANCE new InlineTest$invokeProxy$1()}public void invoke(){System.out.println(eating)}
}可以看出调用高阶函数 proxy会生成内部类该内部类是一个单例将lambda体里的代码拷贝到 invoke 函数里面。
小结简单来说就是有多少个调用点call site 调用高阶函数的地方就会产生多少个内部类。
我们继续往下看如果在 lambda 表达式体里访问外部的变量呢
class InlineTest {var age 18private fun proxy(action: () - Unit) {println(start logging)action()println(end logging)}fun invokeProxy() {proxy {age 11 // 访问外部的成员变量println(eating)}}
}invokeProxy对应的字节码如下 public final invokeProxy()VL0LINENUMBER 34 L0ALOAD 0NEW inline/InlineTest$invokeProxy$1DUPALOAD 0INVOKESPECIAL inline/InlineTest$invokeProxy$1.init (Linline/InlineTest;)VCHECKCAST kotlin/jvm/functions/Function0INVOKESPECIAL inline/InlineTest.proxy (Lkotlin/jvm/functions/Function0;)VL1LINENUMBER 38 L1RETURNL2LOCALVARIABLE this Linline/InlineTest; L0 L2 0MAXSTACK 4MAXLOCALS 1对应的 Java 伪代码如下
public final void invokeProxy(){InlineTest$invokeProxy$1 function0 new InlineTest$invokeProxy$1()proxy(function0)
}该内部类 InlineTest$invokeProxy$1 变成如下:
final class InlineTest$invokeProxy$1 extends kotlin/jvm/internal/Lambda implements kotlin/jvm/functions/Function0 {public void invoke(){InlineTest$invokeProxy$1.this.setAge(11)System.out.println(eating)}
}可以看出每调用一次高阶函数 proxy 会都会创建一个内部类对象。
小结
每个调用高阶函数地方调用点编译时编译器都会生成一个内部类调用高阶函数时如果传入的 lambda 表达式体没有使用外部变量那么只会用到内部类常量对象如果 lambda 表达式体使用了外部变量那么每次调用该高阶函数都会创建一个内部类对象。 如果在一个频繁触发的地方调用高阶函数如自定义 draw 方法里刚好 lambda 体又实用到了外部成员变量这样就会隐性地在 draw 方法里频繁创建对象。
这样时候 inline 关键字就派上用场了。
将上面的高阶函数 proxy 使用 inline 修饰
private inline fun proxy(action: () - Unit) {println(start logging)action()println(end logging)
}调用该高阶函数
fun invokeProxyInline() {proxy {println(eating)}
}字节码对应的 Java 代码如下
public final void invokeProxyInline() {int $i$f$proxyInline false;String var3 start logging;System.out.println(var3);int var4 false;String var5 eating;System.out.println(var5);var3 end logging;System.out.println(var3);
}可以看出调用 proxy 函数的地方不会创建内部类而是将高阶函数的函数体拷贝到调用点。
Non-local returns
什么是 Non-local returns我们先来看下什么是 return下面是 kotlin 对 return 的定义
by default returns from the nearest enclosing function or anonymous function.意思就是从离 return 最近的封闭函数或匿名函数中返回。举个例子
fun test(age:Int) {if (age 0) {return}println(age)
}其中 test就是 enclosing function. 也就是说 return 的作用就是从一个函数中返回离它最近的函数。
搞清楚 return 关键字之后我们来看下在 lambda 中使用 reutrn
// 定义一个普通的高阶函数
private fun normalFunction(action: () - Unit){println(start......)action()println(end......)
}main(){// 调用高阶函数normalFunction {return // 使用 return, 编译器报错}
}发现编译器报错了为啥不能在 lambda 中使用 return 呢
首先上面代码中里离 return关键字最近的 enclosing function是 main函数有人可能会问离 return最近的不是 normalFunction么normalFunction只是一个函数调用它不是一个封闭的函数封闭的是 lambda 表达式。
其次return也无法控制 normalFunction函数的 return的。因为 return所处的代码块只是 normalFunction的 lambda 参数而已return控制的是 lambda。正如 Kotlin 官网所说的
A bare return is forbidden inside a lambda because a lambda cannot make the enclosing function return.因为在 lambda 中使用return无法实现 return 的定义所以无法在 lambda 中使用 return如果是内联函数则可以在 lambda 中使用 return
private inline fun normalFunction(action: () - Unit){println(start......)action()println(end......)
}main(){// 调用高阶函数normalFunction {return}
}上面的代码是合法的因为内联 normalFunction编译时会将代码体拷贝到main函数中.
Kotlin 中把这种 return 称之为 Non-local returnslocated in a lambda, but exiting the enclosing function. Non-local returns 名字很好理解return 的 local 是 lambda而此处的 return 返回的是 lambda 外面的 main 函数non-local所以称之为 non-local returns.
noinline
noinline 顾名思义就是不内联。那是什么时候使用 noinline 呢我们在上面 proxy 函数基础上做一个小修改
private inline fun proxy(action: () - Unit, action2: () - Unit) {println(start logging)action()println(end logging)// action2 作为参数传递给另一个高阶函数cleanResource(action2)
}private fun cleanResource(execution: () - Unit) {execution()println(cleaning resource1)println(cleaning resource2)
}为了 proxy 新增了另一个参数 action2然后将 action2 传递给高阶函数 cleanResource. 但是 IDEA 会提示如下错误 提示我们使用 noinline 修饰 action2 参数
private inline fun proxy(action: () - Unit, noinline action2: () - Unit) {println(start logging)action()println(end logging)// action2 作为参数传递给另一个高阶函数cleanResource(action2)
}为什么不加 noinline 就会报错呢
其实很好理解因为 proxy 是一个 inline 函数那么调用 proxy 的地方编译器都会将函数体拷贝过去包括传入的 lambda 参数如上面的 actionaction2例如
fun invokeProxy() {proxy({println(eating...)}, {println(eating...2)})
}action对应的代码块是println(eating...)action2 对应的代码块是println(eating...2)
因为action2传递给了 cleanResource要想将代码块当做参数传递给函数那么代码块用什么来表示目前只能使用Class类。然而 proxy又是 inline 的所以需要对 action2 参数单独处理将其不要 inline。所以需要使用 oninline关键字来修饰 action2参数。 小结如果需要将 inline 高阶函数的 lambda 参数传递给另一个高阶函数或作为函数的返回值均需要使用 noinline 关键字修饰该参数。 crossinline
介绍完了 inline 和 noinline我们来看下 crossinline。将上面的 proxy 函数稍作修改
private fun wrap(action: () - Unit) {action.invoke()
}private inline fun proxy(action: () - Unit) {println(start logging)wrap {action()}println(end logging)
}编译器会报错给出如下提示 编译器报错原因action 可能包含 Non-local returnsKotlin 中不允许在非内联的 lambda 中使用 return原因已经在 Non-local returns 章节已介绍了也就是说 action 代码块中可能存在 return 关键字需要使用 crossinline 来修饰 action 参数
private inline fun proxy(crossinline action: () - Unit) {println(start logging)wrap {action()}println(end logging)
}// 调用 proxy
private fun invokeProxy() {proxy{println(invoke acrossinline)}
}我们看下 invokeProxy字节码
// 内部类
public final class InlineTest$invokeProxy$$inlined$proxy$1 extends Lambda implements Function0 {public InlineTest$invokeProxy$$inlined$proxy$1() {super(0);}// $FF: synthetic method// $FF: bridge methodpublic Object invoke() {this.invoke();return Unit.INSTANCE;}public final void invoke() {int var1 false;String var2 invoke acrossinline;System.out.println(var2);}
}private final void invokeProxy() {int $i$f$proxy false;String var3 start logging;System.out.println(var3);// 每次都 new 一个内部类对象access$wrap(this, (Function0)(new InlineTest$invokeProxy$$inlined$proxy$1()));var3 end logging;System.out.println(var3);
}可以看出会生成一个内部类
InlineTest$invokeProxy$$inlined$proxy$1
并且每次调用都会创建一个内部类对象。crossinline 阻止了 action 参数内联。
可以到看 crossinline 的核心作用是阻止内联那我们将 crossinline 换成 noinline 是不是也可以呢
private inline fun proxy(noinline action: () - Unit) {println(start logging)wrap {action()}println(end logging)
}编译器不会报错代码运行也是OK貌似可以使用 noinline 代替 crossinline既然可以用 noinline为啥还搞个新的 crossinline 关键字上面的代码虽然可以使用 noinline 代替 crossinline但是底层还差别的。我们看下使用 noinline 对应的字节码 public final invokeProxy()VL0LINENUMBER 135 L0ALOAD 0ASTORE 1GETSTATIC inline/InlineTest$invokeProxy$1.INSTANCE : Linline/InlineTest$invokeProxy$1;CHECKCAST kotlin/jvm/functions/Function0// 省略其他代码L5LINENUMBER 184 L5ALOAD 1NEW inline/InlineTest$proxy$1DUPALOAD 2INVOKESPECIAL inline/InlineTest$proxy$1.init (Lkotlin/jvm/functions/Function0;)VCHECKCAST kotlin/jvm/functions/Function0INVOKESTATIC inline/InlineTest.access$wrap (Linline/InlineTest;Lkotlin/jvm/functions/Function0;)V// 省略其他...可见使用 noinline 的话会创建两个内部类
inline/InlineTest$invokeProxy$1inline/InlineTest$proxy$1
至此inline、noinline 和 crossinline 就介绍完毕了。