写点什么

Kotlin 函数声明与闭包

作者:子不语Any
  • 2022-11-16
    湖南
  • 本文字数:1787 字

    阅读完需:约 6 分钟

Kotlin函数声明与闭包

前言

本文介绍闭包。闭包其实不算是新东西了,Kotlin 就基本没有多少新东西,甚至可以说新型编程语言基本都没有新东西。是把先前编程语言好用的特性组装起来,再加一部分拓展。

本文大纲

1. 闭包介绍

首次接触 闭包 应该是在 JavaScript 上,有函数为“一等公民”特性的编程语言都有这个概念。函数是“一等公民”的意思是,函数跟变量一样,是某种类型的实例,可以被赋值,可以被引用。函数还可以被调用。变量类型是某个声明的类,函数类型就是规定了入参个数,类型和返回值类型(不规定名字。函数名就和变量名一样,任意定义但要符合规则)。


如要声明 Kotlin 一个函数类型,入参是两个整数,出参是一个整数,那应该这样写:val add: (Int, Int) -> Int。箭头左边括号内表示入参,括号不可省略。箭头右边表示返回值。


wiki上闭包的概念是:引用了自由变量的函数,这个被引用的自由变量将和这个函数一同存在。从定义来说,对闭包的理解,是基于普通函数之上的。一般的函数,能处理的只有入参和全局变量,然后返回一个结果。闭包比普通函数功能更强,可以获取当前上下文的局部变量。当然了,捕获局部变量的前提是可以在局部环境里声明一个函数,这只有把函数当作“一等公民”才可以做到。

2. 闭包与匿名类比较

Java 中,匿名类其实就是代替闭包而存在的。不过 Java 严格要求所有函数都需要在类里面,所以巧妙的把“声明一个函数”这样的行为变成了“声明一个接口”或“重写一个方法”。匿名类也可以获取当前上下文的 final 局部变量。和闭包不一样的是,匿名类无法修改获取的局部变量 final 不可修改


匿名类能引用 final 局部变量,是因为在编译阶段,会把该局部变量作为匿名类的构造参数传入。


Java8 lambda 是进一步接近闭包的特性,lambda 的 JVM 实现是类似函数指针的东西。但 Java7 中的 lambda 语法糖兼容不算是真正的 lambda,只是简化了匿名类的书写。

3. 闭包使用

来看一个闭包的例子:


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


分析上面的代码,returnFun返回了一个函数,这个函数没有入参,返回值是Int。可以用变量接收它,还可以调用它。functionfunction2分别是创建的两个函数实例。


可以看到,每调用一次function()count都会加一,说明countfunction持有了而且可以被修改。而function2functioncount是独立的,不是共享的。


而通过 jadx 反编译可以看到:


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,还有 LongRefFloatRef 等,如果是非基础类型,就统一用 ObjectRef 即可。Ref 家族源码:Ref.java


Java 中,如果想要匿名类来操作外部变量,一般做法是把这个变量放入一个 final 数组中。这和 Kotlin 的做法本质上是一样的,即通过持有该变量的引用来使得两个类可以修改同一个变量。

4. 总结

根据上面示例分析,可以总结出:


  • 闭包不算是新东西,是把函数作为“一等公民”的编程语言的特性;

  • 匿名类是 Java 世界里的闭包,但有局限性,即只能读 final 变量,不能写任何变量;

  • Kotlin 的闭包可以获取上下文的局部变量,并可以修改它。实现办法是 Kotlin 编译器给引用的局部变量封装了一层引用。

发布于: 刚刚阅读数: 4
用户头像

子不语Any

关注

If not now,when? 2022-09-17 加入

安卓攻城狮

评论

发布
暂无评论
Kotlin函数声明与闭包_android_子不语Any_InfoQ写作社区