函数探究

kotlin中的函数不同于Java中的方法,函数在kotlin中是一级公民,也就是说函数可以作为引用,出现在变量、参数、返回值等其它类能出现的位置,并且使用函数的引用可以直接调用函数。

分析函数的实质

在数学中,函数就是一个映射,从定义域到值域的一一对应关系。

如果把函数当做引用,那么重要的内容是什么呢?

  1. 定义域:即为方法的入参类型
  2. 值域:即为方法的返回值类型
  3. 函数的具体逻辑:这个对应的不是引用,而是引用的实体,所以在函数引用中,具体逻辑是不需要在引用中实现的

函数应用

作用范围

函数的作用域与其它引用无异,范围有以下等

  1. 变量
  2. 参数
  3. 返回值 ```kotlin // 函数作为顶部变量 var function1: ((i: Int) -> String)? = null

// 函数作为参数 var function2: (((Int) -> String) -> Unit)? = null

// 函数作为返回值 var function3: (() -> (Int) -> String)? = null

  1. **tips:**函数作为引用的时候,参数的类型必须加上,而参数的名称可以省略<br />(这很符合数学函数的特性,定义域 x∈(12)或者 a∈(12),都不影响函数的表达)
  2. <a name="oMRP5"></a>
  3. ## 自动推断函数返回值
  4. 函数的返回值如果可以通过一行代码自动推断出来,就可以去掉`{}`,换上`=`
  5. ```kotlin
  6. fun add(i: Int, j: Int) = i + j

有时候复杂的情形不容易一眼看出,但是只要找到 fun 或者 ->就可以知道这是一个函数

  1. inline fun <reified T : ViewBinding> inflateBinding(layoutInflater: LayoutInflater) =
  2. T::class.java.getMethod("inflate", LayoutInflater::class.java)
  3. .invoke(null, layoutInflater) as T

函数赋值

函数当做引用,当然需要赋值的时刻,就相当于类引用被赋值,才能真正执行

  1. // 接收一个函数作为参数
  2. fun addFunction(num1: Int, num2: Int, block: (Int, Int) -> Int) {
  3. val num3 = block(num1, num2)
  4. println("我是调用了传入函数 $block 计算了2个值 : $num3")
  5. }
  6. // 测试函数的完整调用
  7. fun invokeAddFunction() {
  8. // 1.完整调用
  9. addFunction(1, 2, fun(i: Int, j: Int): Int {
  10. return i + j
  11. })
  12. // 2.完整调用+省略参数类型
  13. addFunction(1, 2, fun(i, j): Int {
  14. return i * j
  15. })
  16. // 3.完整调用+省略参数类型+自动推断返回值
  17. addFunction(1, 2, fun(i, j) = i + j)
  18. // 4.lambda表达式调用
  19. addFunction(1, 2, { i: Int, j: Int ->
  20. i * j
  21. })
  22. // 5.lambda表达式调用+省略参数类型
  23. addFunction(1, 2, { i, j ->
  24. i + j
  25. })
  26. // 6.lambda表达式调用+省略参数类型+最后一个参数为lambda可以写在()外部
  27. addFunction(1, 2) { i, j ->
  28. i * j
  29. }
  30. // 7.使用方法引用作为函数
  31. addFunction(1, 2, ::add)
  32. }

总结:

  1. 使用 fun 完整模式,但是写起来代码多,很少使用
  2. 使用 lambda表达式,省略参数类型最后一行作为返回值参数最后一个为lambda的时候可以写在()外面
  3. 使用::,这是方法的是引用,Java中也有,只要方法接收的参数类型、个数、返回值与函数的引用相同,即可以直接传入方法引用

常用函数

内联函数

内联函数是kotlin中一种特殊函数,与C的内联特类似,都是把代码拷贝到使用的位置
优点:

  1. 减少内存的开销
  2. 可以使用refine等真实泛型

缺点:

  1. 增加代码量,所以内联函数不宜内部代码过多

内联函数和普通函数的区别

  1. // 内联函数
  2. inline fun attack(i: Int, block: (Int) -> Int): String {
  3. return block(i).toString()
  4. }
  5. // 普通函数
  6. fun attack2(i: Int, block: (Int) -> Int): String {
  7. return block(i).toString()
  8. }

反编译

  1. @NotNull
  2. public static final String attack(int i, @NotNull Function1 block) {
  3. int $i$f$attack = 0;
  4. Intrinsics.checkNotNullParameter(block, "block");
  5. return String.valueOf(((Number)block.invoke(i)).intValue());
  6. }
  7. @NotNull
  8. public static final String attack2(int i, @NotNull Function1 block) {
  9. Intrinsics.checkNotNullParameter(block, "block");
  10. return String.valueOf(((Number)block.invoke(i)).intValue());
  11. }

结论:

  1. 内联函数与普通函数没有任何区别

Kotlin调用内联函数

  1. fun main() {
  2. // kotlin调用内联函数、普通函数的区别
  3. attack(1) { i -> i + 1 }
  4. println("--------------------------")
  5. attack2(1) { i -> i + 1 }
  6. }

反编译

  1. public static final void main() {
  2. int i$iv = 1; // 内联函数直接代码拷贝
  3. int $i$f$attack = false;
  4. int var3 = false;
  5. String.valueOf(i$iv + 1);
  6. String var4 = "--------------------------";
  7. $i$f$attack = false;
  8. System.out.println(var4);
  9. attack2(1, (Function1)null.INSTANCE); // 普通函数传Function1
  10. }

结论:

  1. 内联函数直接拷贝代码到了调用处,没有使用接口
  2. 普通函数使用了自动生成的接口Function1

Java调用内联函数

  1. public static void main(String[] args) {
  2. // 没有任何区别,都是传入Function1回调,所以java不存在内联特性
  3. KtInlineKt.attack(1, i -> i + 1);
  4. KtInlineKt.attack2(1, i -> i + 1);
  5. }

结论:

  1. Java不存在内联特性

扩展函数

Kotlin中最常用的函数特性就是扩展函数,可以对已知类无侵入式的扩展,并且能直接调用,猜测扩展函数是通过装饰模式(代理模式)实现的

探究扩展函数的实质

  1. // 一个简单Kotlin类,等待被扩展
  2. class Source {
  3. private val tag = "普通Kotlin类Source"
  4. fun plus(num1: Int, num2: Int): Int {
  5. println("$tag ---调用了plus()")
  6. return num1+num2
  7. }
  8. }
  1. // 扩展函数
  2. fun Source.plusPlus(num1: Int, num2: Int) {
  3. // 这里取到的this,是Source对象
  4. val plus = this.plus(num1, num2)
  5. val outPlus = plus + num2
  6. println("外部调用了plusPlus,多加了1次num2,结果为:$outPlus")
  7. }

反编译

  1. public final class SourceExtendKt {
  2. public static final void plusPlus(@NotNull Source $this$plusPlus, int num1, int num2) {
  3. Intrinsics.checkNotNullParameter($this$plusPlus, "$this$plusPlus");
  4. int plus = $this$plusPlus.plus(num1, num2);
  5. int outPlus = plus + num2;
  6. String var5 = "外部调用了plusPlus,多加了1次num2,结果为:" + outPlus;
  7. boolean var6 = false;
  8. System.out.println(var5);
  9. }
  10. }

结论:

  1. 扩展函数并不改变原类
  2. 扩展函数也不是装饰模式,而是根据类名生成了一个新类
  3. 接收了一个原类的对象为参数,同时接收扩展函数自己定义的参数

使用扩展函数

Kotlin使用

  1. fun main() {
  2. val source = Source()
  3. source.plusPlus(1,2)
  4. }

非常简单,就像调用原来类上的函数一样,反编译看看

  1. public final class KtInvokeExtendKt {
  2. public static final void main() {
  3. Source source = new Source();
  4. SourceExtendKt.plusPlus(source, 1, 2);
  5. }
  6. }

结论:

  1. kotlin调用扩展很方便,在被扩展的原类上调用即可
  2. 实质上,并不是修改了原类,而是通过扩展方法,接收原类对象来调用

Java使用

  1. public static void main(String[] args) {
  2. // source对象没有plusPlus方法,所以kotlin函数并没有真正修改Source类
  3. Source source = new Source();
  4. source.plus(1, 2);
  5. // 扩展函数是 SourceExtendKt,没有plus方法,并且需要传入Source对象
  6. SourceExtendKt.plusPlus(source, 1, 2);
  7. }

结论:

  1. Java使用扩展函数就像反编译kotlin使用代码一样

A.(B) -> C 类型函数

函数类型可以有一个额外的接收者类型,它在表示法中的点之前指定: 类型 A.(B) -> C 表示可以在 A 的接收者对象上以一个 B 类型参数来调用并返回一个 C 类型值的函数
在这样的函数字面值内部,传给调用的接收者对象成为隐式_this_,以便访问接收者对象的成员而无需任何额外的限定符,亦可使用 this表达式 访问接收者对象。
这种行为与扩展函数类似,扩展函数也允许在函数体内部访问接收者对象的成员。

  1. class Amber {
  2. fun attack(i: Int) {
  3. println("Amber 攻击了${i}次")
  4. }
  5. }
  1. // A.(B) -> C 函数
  2. val block: Amber.(String) -> Amber = { str ->
  3. // 内部空间在Amber对象内,可以直接调用Amber的方法
  4. attack(str.toInt())
  5. this
  6. }

A.(B) -> C 函数的好处在于函数内部直接就是接受者对象的范围,用this就可以直接使用接受者方法
反编译

  1. @NotNull
  2. private static final Function2 block;
  3. @NotNull
  4. public static final Function2 getBlock() {
  5. return block;
  6. }
  7. /** A function that takes 2 arguments. */
  8. public interface Function2<in P1, in P2, out R> : Function<R> {
  9. /** Invokes the function with the specified arguments. */
  10. public operator fun invoke(p1: P1, p2: P2): R
  11. }

结论:

  1. 额外接受者类型函数,还是把接收类型的对象+参数生成了Function接口
  2. 那么Kotlin使用就有2种方式
  3. Java调用只有1种方式

验证上面的结论

  1. fun main() {
  2. // 使用方法1,函数引用block作为调用者
  3. block(Amber(), "12")
  4. // 使用方法2,接收函数的对象作为调用者,block作为方法
  5. Amber().block("13")
  6. }

反编译

  1. public static final void main() {
  2. block.invoke(new Amber(), "12");
  3. block.invoke(new Amber(), "13");
  4. }

结论:

  1. Kotlin调用方法确实有2种
  2. 通过接收类对象调用,是kotlin的语法而已,实质还是函数调用

相关内容

常用的kotlin扩展函run(),apply()等就是通过此类型函数实现内部范围this调用