当前位置 : 主页 > 编程语言 > java >

kotlin中闭包的概念和原理

来源:互联网 收集:自由互联 发布时间:2023-02-04
kotlin中闭包的概念和原理 问题背景 安卓开发,kotlin学习和使用过程中,kotlin闭包肯定算是个新概念,尤其从安卓java开发转kotlin的同学,现在一起看看kotlin的闭包到底是什么意思?闭包

kotlin中闭包的概念和原理

问题背景

安卓开发,kotlin学习和使用过程中,kotlin闭包肯定算是个新概念,尤其从安卓java开发转kotlin的同学,现在一起看看kotlin的闭包到底是什么意思? 闭包的概念关键:外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象,这就是闭包的重要概念。简单的说就是,一个函数A可以访问另一个函数B的局部变量,即便另一个函数B执行完成了也没关系。 直接看概念估计不是很好理解,一起从实例看看具体是怎么回事?

问题分析

(1)java闭包相关

1、不完整闭包 安卓开发中,java匿名内部类使用外部的变量必现是final的,这就使我们不能在匿名内部类里面修改外部变量的值,代码如下所示: ``` final Integer data = 0; // 必须是final

View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { Log.d(TAG, " data = " + data); } }; findViewById(R.id.button).setOnClickListener(listener); ``` 根本原因在于方法内的临时变量是存放在栈区的,一旦方法调用完成,这部分的内存就会被释放。如果我们再去修改这块的内存就会造成不可预期的后果。那么问题又来了,如果一旦方法调用完成,上面例子的data这个引用变量的内存就会被回收。那么在onClick的时候为什么还能读取到值呢? java实现匿名内部类的原理是Java编译器会给它生成一个实际的类,将外部变量保存到这个类的成员变量里,我们可以通过下面的代码打印出这个生成的类:

Log.d(TAG, "class " + listener.getClass() + " { "); for (Field field : listener.getClass().getDeclaredFields()) { StringBuilder sb = new StringBuilder(); sb.append("\t"); if (Modifier.isFinal(field.getModifiers())) { sb.append("final "); } sb.append(field.getType().getName()) .append(" ") .append(field.getName()) .append(";"); Log.d(TAG, sb.toString()); } Log.d(TAG, "}");

可以看到这个类除了用成员变量保存了外部的data的副本之外,还保存了外部类MainActivity的引用。这也是匿名内部类/非静态内部类持有外部类引用的原理。 2、突破java不完整闭包 知道了Java匿名内部类持有外部对象引用的原理之后,我们其实是可以通过下面的方法绕过不能修改外部对象的限制的:

final Integer[] data = new Integer[]{0}; View.OnClickListener listener = new View.OnClickListener() { @Override public void onClick(View v) { data[0]++; Log.d(TAG, " data = " + data[0]); } };

这种做法的原理在于,data这个引用本身的内存在栈区,方法调用完会被回收,但是它所指向的数组的内存在堆区,只要还有引用指向它就不会被回收。而Java编译器生成的这个类巧合就有个成员变量保存了data的副本,指向了这个数组。

(2)Kotlin闭包原理

Kotlin闭包的原理实际上就是上面讲的突破Java不完整闭包限制的原理。可以查看Kotlin代码生成的Java字节码。 1、Android studio查看生成的java字节码的步骤 kotlin查看kotlin字节码 image.png 将kotlin字节码反编译为java代码 image.png 2、kotlin代码如下:

var data = 0 findViewById<Button>(R.id.button).setOnClickListener { data++ Log.d(TAG,"data = $data") }

3、查看对应编译的java代码如下:

final IntRef data = new IntRef(); data.element = 0; ((Button)this.findViewById(1000095)).setOnClickListener((OnClickListener)(new OnClickListener() { public final void onClick(View it) { int var10001 = data.element++; Log.d(MainActivity.this.TAG, "data = " + data.element); } }));

(3)Kotlin闭包函数

现在看一个kotlin闭包函数的例子,代码如下:

fun returnFun(): () -> Int { var count = 0 return { count++ } } fun main() { val function = returnFun() val function2 = returnFun() println(function()) // 0 println(function()) // 1 println(function2()) // 0 println(function2()) // 1 }

分析上面的代码,returnFun返回了一个函数,这个函数没有入参,返回值是Int。可以用变量接收它,还可以调用它。function和function2分别是创建的两个函数实例。 可以看到,每调用一次function(),count都会加一,说明count 被function持有了而且可以被修改。而function2和function的count是独立的,不是共享的。 通过(2)中介绍方法反编译可以看到:

public final class ClosureKt { @NotNull public static final Function0<Integer> returnFun() { IntRef intRef = new IntRef(); intRef.element = 0; return (Function0) new 1<>(intRef); } public static final void main() { Function0 function = returnFun(); Function0 function2 = returnFun(); System.out.println(((Number) function.invoke()).intValue()); System.out.println(((Number) function.invoke()).intValue()); System.out.println(((Number) function2.invoke()).intValue()); System.out.println(((Number) function2.invoke()).intValue()); } }

被闭包引用的 int 局部变量,会被封装成 IntRef 这个类。 IntRef 里面保存着 int 变量,原函数和闭包都可以通过 intRef 来读写 int 变量。Kotlin 正是通过这种办法使得局部变量可修改。除了 IntRef,还有 LongRef,FloatRef 等,如果是非基础类型,就统一用 ObjectRef 即可。

问题总结

再总结一下闭包的概念,外部函数调用之后其变量对象本应该被销毁,但闭包的存在使我们仍然可以访问外部函数的变量对象。Kotlin 的闭包可以获取上下文的局部变量,并可以修改它。实现办法是 Kotlin 编译器给引用的局部变量封装了一层引用。

网友评论