一文吃透 Kotlin 中眼花缭乱的函数家族...(2)

简介: 一文吃透 Kotlin 中眼花缭乱的函数家族...(2)

5. 内联函数

inline 函数在调用它的地方,会把这个函数方法体中的所以代码移动到调用的地方,而不是通过方法间压栈进栈的方式。一定程度上可以代码效率。

比如如下的代码:

class TestInline {
    fun test() {
        highLevelFunction("Android") {
            it.length
        }
    }
    private fun highLevelFunction(input: String, mapper: (String) -> Int): Int {
        Log.d("TestInline", "highLevelFunction input:$input")
        return mapper(input)
    }
}

highLevelFunction 函数没有添加 inline 的话,反编译之后可以看到 test 函数调用 highLevelFunction 的时候传入了 Function1 接口实例。

public final class TestInline {
   public final void test() {
      this.highLevelFunction("Android", (Function1) ...);
   }
   private final int highLevelFunction(String input, Function1 mapper) {
      Log.d("TestInline", Intrinsics.stringPlus("highLevelFunction input:", input));
      return ((Number)mapper.invoke(input)).intValue();
   }
}

当添加了 inline 修饰再看下反编译的代码,会发现 highLevelFunction 函数的内容被编译进了 test 函数内。

public final class TestInline {
   public final void test() {
      String input$iv = "Android";
      int $i$f$highLevelFunction = false;
      Log.d("TestInline", "highLevelFunction input:" + input$iv);
      int var5 = false;
      input$iv.length();
   }
}

但并非所有的函数都适合 inline 标注,强行标注的话会收到 IDE 的警告:


Expected performance impact of inlining ‘…’ can be insignificant. Inlining works best for functions with lambda parameters.


是否使用 inline 函数,可以简单参考如下:


不带参数,或是带有普通参数的函数,不建议使用 inline

带有 lambda 函数参数的函数,建议使用 inline

另外,inline 还可以让函数参数里面的 return 生效。因为平常的高阶函数调用传入方法体不允许 return,但如果该高阶函数标注了 inline 就可以直接 return 整个外部函数。

class TestInline {
    fun test() {
        highLevelFunction("Android") {
            it.length
            return // 可以 return 整个 test 函数,后续的 Log 不再输出
        }
        Log.d("TestInline", "tested")
    }
    private inline fun highLevelFunction(input: String, mapper: (String) -> Int): Int {
        ...
    }
}

反编译之后会发现,由于 return 的存在,后续的 Log 代码压根没参与编译。

6. 闭包

前面提到的 lambda 表达式或匿名函数可以访问其闭包,即便是作用域以外的局部变量,甚至可以进行修改。

比如下面的 stringMapper 的 lambda 参数内可以直接访问和修改外部的 sum 变量。

    fun test() {
        var sum = 0
        stringMapper("Android") {
            sum += it.length
            ...
        }
        print(sum)
    }

反编译后可以看到传入 stringMapper 高阶函数的是 Function1 接口的实现即匿名内部类,匿名内部类拷贝 sum 引用进行数值的修改操作。

   public final void test() {
      final IntRef sum = new IntRef();
      sum.element = 0;
      this.stringMapper("Android", (Function1)(new Function1() {
         public Object invoke(Object var1) {
            return this.invoke((String)var1);
         }
         public final int invoke(@NotNull String it) {
            Intrinsics.checkNotNullParameter(it, "it");
            IntRef var10000 = sum;
            var10000.element += it.length();
            return it.length();
         }
      }));
      int var3 = sum.element;
      System.out.print(var3);
   }

如果高阶函数同时也是内联函数,那么实现较为直接,即在外部函数内直接操作变量即可。

   public final void test() {
      int sum = 0;
      String input$iv = "Android";
      int $i$f$stringMapper = false;
      int var7 = false;
      int sum = sum + input$iv.length();
      input$iv.length();
      System.out.print(sum);
   }

7. 顶层函数

Kotlin 允许在文件内直接定义函数,这个方法可以被称为顶层函数

// Test.kt
fun topFunction(string: String) {
    println("this is top function for $string")
}

这种函数可以在 Kotlin 中被直接调用,无需指定其实例或类名。

class TestInline {
    fun test() {
        ...
        topFunction("Ellison")
    }
}

在 Java 中调用该顶层函数的话是和扩展函数一样的形式:

public class TestJava {
    static void main(String[] args) {
        TestKt.topFunction("Ellison");
    }
}

通过反编译会发现,原理跟扩展函数一样,其通过静态函数实现:

public final class TestKt {
   ...
   public static final void topFunction(@NotNull String string) {
      Intrinsics.checkNotNullParameter(string, "string");
      String var1 = "this is top function for " + string;
      System.out.println(var1);
   }
}

调用处反编译的实现也可想而知。

public final class TestInline {
   public final void test() {
      TestKt.topFunction("Ellison");
   }
}

留意下和扩展函数的区别:

  1. 定义的时候无需指定目标类名
  2. 调用的时候自然无需指定实例

8. 局部函数

除了允许在文件顶层定义函数外,Kotlin 还允许在现有函数内定义嵌套函数,称为局部函数

而且该函数还可以访问闭包。

    fun magic(): Int {
        val v1 = (0..100).random()
        fun foo(): Int {
            return v1 * v1
        }
        return foo()
    }

通过反编译发现和闭包是一样的实现:

public final int magic() {
      byte var2 = 0;
      IntRange var3 = new IntRange(var2, 100);
      final int v1 = RangesKt.random(var3, (Random)Random.Default);
      <undefinedtype> $fun$foo$1 = new Function0() {
         public Object invoke() {
            return this.invoke();
         }
         public final int invoke() {
            return v1 * v1;
         }
      };
      return $fun$foo$1.invoke();
   }

当然如果没有访问闭包的话。

    fun magic(): Int {
        val v1 = (0..100).random()
        fun foo(v: Int): Int {
            return v * v
        }
        return foo(v1)
    }

实现稍稍区别:

   public final int magic() {
      byte var2 = 0;
      IntRange var3 = new IntRange(var2, 100);
      int v1 = RangesKt.random(var3, (Random)Random.Default);
      <undefinedtype> $fun$foo$1 = null.INSTANCE;
      return $fun$foo$1.invoke(v1);
   }

9. 运算符重载函数

Kotlin 允许使用 operator 关键字对已有函数进行重载,达到扩展函数参数、改写函数逻辑等目的。

比如如下给 Int 类型的 minus 方法扩展了支持 Person 参数的重载函数,这样的话数字即可与 Person 类型直接进行 - 号运算。

    data class Person(var name: String, var age: Int)
    operator fun Int.minus(p: Person) =  this - p.age
    fun testOperator() {
        val person1 = Person("A", 3)
        println("testInt+person1=${5 - person1}")
    }

如果不添加 operator 运算符的话,上述 5 - person1 的写法会无法通过编译,因为 - 运算符不识别 Person 类型。因为这种写法就变成了向 Int 类添加了接收 Person 参数的 minus 方法而已,使用的话就得要改成对象调用函数的形式:

    ...
    fun Int.minus(p: Person) =  this - p.age
    fun testOperator() {
        val person1 = Person("A", 3)
        println("testInt+person1=${5.minus(person1)}")
    }

除此之外还可以重载 get()compareTo() 等函数,在这里还想额外谈下运算符重载函数在解构声明方面的用处。

解构声明

Kotlin 有时会把一个对象解构成很多变量,使用起来会很方便:

    fun testDeco() {
        val (msg, code) = Result("good", 1)
        println("msg:${msg} code:${code}")
    }
    inner class Result(
        private val msg: String,
        private val code: Int
    ) {
        operator fun component1() = msg
        operator fun component2() = code
    }

componentN() 函数是 Kotlin 中约定的获取解构声明变量的对应运算符,这里面需要使用 operator 进行描述以重载。

解构声明的变量如果不需要使用的话,可以用 _ 来代替,比如:

val (_, status) = getResult()

解构声明在 Kotlin 中使用非常普遍,比如遍历一个映射(map)最好的方式:

for ((key, value) in map) {
   ...
}

operator 原理

反编译解构声明的示例代码,可以看到事实上定义了多个变量,每个变量按照顺序调用目标类中实现的 componentN() 函数进行赋值。

public final void testDeco() {
      Test.Result var3 = new Test.Result("good", 1);
      Object msg = var3.component1();
      int code = var3.component2();
      String var4 = "msg:" + msg + " code:" + code;
      System.out.println(var4);
}
public final class Result {
      private final String msg;
      private final int code;
      @NotNull
      public final Object component1() {
         return this.msg;
      }
      public final int component2() {
         return this.code;
      }
      ...
   }

结语

函数在 Kotlin 语法中极为重要,了解其特点和原理对于灵活编程非常必要,再次回顾下各函数的异同及原理。

1672192792217.png

参考

https://developer.android.com/kotlin/learn


Kotlin Functions


https://developer.android.com/kotlin/ktx?hl=zh-cn


Kotlin中,理解T.()->Unit 、 ()->Unit与(T) -> Unit


kotlin—匿名函数及其实现原理


Kotlin匿名函数


避免滥用 Kotlin 扩展函数


Kotlin 内联函数 inline


让人爱不释手的Kotlin扩展(Extensions)技术探Extensions秘与应用


相关文章
|
21天前
|
安全 Kotlin
Kotlin教程笔记(23) -作用域函数
Kotlin教程笔记(23) -作用域函数
58 6
|
21天前
|
Kotlin
Kotlin教程笔记(21) -高阶函数与函数引用
Kotlin教程笔记(21) -高阶函数与函数引用
30 6
|
1月前
|
IDE 开发工具 Kotlin
Kotlin - 函数与Lambda表达式
Kotlin - 函数与Lambda表达式
42 13
|
25天前
|
IDE 开发工具 Kotlin
Kotlin教程笔记(6) - 函数与Lambda表达式
Kotlin教程笔记(6) - 函数与Lambda表达式
49 1
|
1月前
|
IDE 开发工具 Kotlin
Kotlin - 函数与Lambda表达式
Kotlin - 函数与Lambda表达式
25 1
|
1月前
|
IDE 开发工具 Kotlin
Kotlin - 函数与Lambda表达式
Kotlin - 函数与Lambda表达式
|
1月前
|
安全 Kotlin
Kotlin - 作用域函数
Kotlin - 作用域函数
|
1月前
|
Kotlin
Kotlin教程笔记(21) -高阶函数与函数引用
Kotlin教程笔记(21) -高阶函数与函数引用
|
1月前
|
安全 Kotlin
Kotlin教程笔记(23) -作用域函数
Kotlin教程笔记(23) -作用域函数
|
1月前
|
安全 Kotlin
Kotlin - 作用域函数
Kotlin - 作用域函数
24 0