kotlin系列知识卡片 - 拓展函数

149 阅读3分钟

前言
随着kotlin在Android日常开发中越来越频繁地使用,笔者觉得有必要对kotlin的一些特性做一些原理的梳理,所谓知其然知其所以然,才能更好让kotlin服务于我们的业务开发,所以会做一个kotlin系列,每个知识点简短,像个小卡片一样,便于理解和快速回顾。所有的源码为:Kotlin 标准库版本为1.9.10。

拓展函数

拓展函数是kotlin中的一种特性,允许在不修改原始类的情况下,为一个类添加新的方法。拓展函数的语法类似普通函数的定义,但需要在函数前添需要拓展的类的前缀,作为这个函数的接收者。这种特性在日常开发中为我们提供了更强大的代码的扩展性和编码的灵活性和可读性。

使用

拓展函数

demo中,我们为定义了一个StringPlus数据类型。为了保证数据类型的纯粹性,它本身并无其他自定义函数,但在这个数据对象的使用中可能涉及一些操作,这里为这个数据类型,我们拓展了StringPlus的运算符重载函数:

operator fun List<StringPlus>.plus(str: StringPlus): List<StringPlus> {
    return this + listOf(str)
}

operator fun StringPlus.plus(strs: List<StringPlus>): List<StringPlus> {
    return listOf(this) + strs
}

operator fun StringPlus.plus(str: StringPlus): List<StringPlus> {
    return listOf(this, str)
}

在业务中,可以直接对StringPlus对象使用+号就可以得到一个StringPlus对象的列表:

class Demo {
    private val strs = mutableListOf<StringPlus>()

    fun test1(vararg str: StringPlus): List<StringPlus> {
        str.forEach { strs + it }
        return strs
    }

    fun test2(vararg str: StringPlus): List<StringPlus> {
        str.forEach { it + strs }
        return strs
    }

    fun test3(str1: StringPlus, str2: StringPlus): List<StringPlus> {
        return str1 + str2
    }
}

原理

拓展函数的反编译

拓展函数的本质是生成一个静态函数,将接收这个函数的类的对象作为参数,传递进这个静态函数中,并将拓展函数中的this替换为接收类的实例对象。拓展函数支持为空对象做拓展,所以如果拓展函数未对接收类增加?标志,则生成的kotlin静态函数都会对接收对象做空判断。

从反编译代码中,我们可以理解为何官方文档提到拓展函数是一种静态拓展,以及这种设计中来带的限制:

  • 继承受限:拓展函数并不能在继承关系中被子类覆写
  • 访问受限:无法访问私有成员变量和私有函数
@Metadata(...)
public final class TestKt {
   @NotNull
   public static final List plus(@NotNull List $this$plus, @NotNull StringPlus str) {
      Intrinsics.checkNotNullParameter($this$plus, "$this$plus");
      Intrinsics.checkNotNullParameter(str, "str");
      return CollectionsKt.plus((Collection)$this$plus, (Iterable)CollectionsKt.listOf(str));
   }

   @NotNull
   public static final List plus(@NotNull StringPlus $this$plus, @NotNull List strs) {
      Intrinsics.checkNotNullParameter($this$plus, "$this$plus");
      Intrinsics.checkNotNullParameter(strs, "strs");
      return CollectionsKt.plus((Collection)CollectionsKt.listOf($this$plus), (Iterable)strs);
   }

   @NotNull
   public static final List plus(@NotNull StringPlus $this$plus, @NotNull StringPlus str) {
      Intrinsics.checkNotNullParameter($this$plus, "$this$plus");
      Intrinsics.checkNotNullParameter(str, "str");
      return CollectionsKt.listOf(new StringPlus[]{$this$plus, str});
   }
}

调用拓展函数处的代码反编译

调用处则是将拓展函数替换为静态函数的调用。

@Metadata(...)
public final class Demo {
   private final List strs = (List)(new ArrayList());

   @NotNull
   public final List test1(@NotNull StringPlus... str) {
      Intrinsics.checkNotNullParameter(str, "str");
      Object[] $this$forEach$iv = str;
      int $i$f$forEach = false;
      int var4 = 0;

      for(int var5 = str.length; var4 < var5; ++var4) {
         Object element$iv = $this$forEach$iv[var4];
         int var8 = false;
         TestKt.plus(this.strs, element$iv);
      }

      return this.strs;
   }

   @NotNull
   public final List test2(@NotNull StringPlus... str) {
      Intrinsics.checkNotNullParameter(str, "str");
      Object[] $this$forEach$iv = str;
      int $i$f$forEach = false;
      int var4 = 0;

      for(int var5 = str.length; var4 < var5; ++var4) {
         Object element$iv = $this$forEach$iv[var4];
         int var8 = false;
         TestKt.plus(element$iv, this.strs);
      }

      return this.strs;
   }

   @NotNull
   public final List test3(@NotNull StringPlus str1, @NotNull StringPlus str2) {
      Intrinsics.checkNotNullParameter(str1, "str1");
      Intrinsics.checkNotNullParameter(str2, "str2");
      return TestKt.plus(str1, str2);
   }
}