Kotlin-扩展(Extension)的能力边界在哪?

1、扩展函数

我们对String定义一个扩展函数

//对String 增加扩展函数lastElement
//直接定义在kotlin文件里,称之为顶层扩展、
fun String.lastElement(): Char? {
    if (this.isEmpty()) {
        return null
    }
    return this[length - 1]
}
fun main() {
    val a = "Hello World"
    println(a.lastElement())
}

扩展函数我们定义在kotlin文件中,称之为顶层扩展,任何地方都可以使用,转成Java代码看实现


public final class ExtentionKt {
   @Nullable
   public static final Character lastElement(@NotNull String $this$lastElement) {
      Intrinsics.checkNotNullParameter($this$lastElement, "$this$lastElement");
      CharSequence var1 = (CharSequence)$this$lastElement;
      boolean var2 = false;
      return var1.length() == 0 ? null : $this$lastElement.charAt($this$lastElement.length() - 1);
   }

   public static final void main() {
      String a = "Hello World";
      Character var1 = lastElement(a);
      boolean var2 = false;
      System.out.println(var1);
   }

}

其实实现很简单,定义了一个在ExtentionKt 类中定义了一个lastElement静态方法并传入String对象。在调用的时候,其实就是传入了我们的字符串a即可。然后再算出最后一个字符。
所以 Kotlin 编译器会将扩展函数转换成对应的静态方法,而扩展函数调用处的代码也会被转换成静态方法的调用。

2、扩展属性

//对String 增加扩展函数lastElement
//直接定义在kotlin文件里,称之为顶层扩展、
fun String.lastElement(): Char? {
    if (this.isEmpty()) {
        return null
    }
    return this[length - 1]
}
//定义扩展属性
val String.firstElement: Char?
    get() = if (isEmpty()) null else this[0]
fun main() {
    val a = "Hello World"
    println(a.lastElement())
    println(a.firstElement)

}

转成Java代码看实现

public final class ExtentionKt {
   @Nullable
   public static final Character lastElement(@NotNull String $this$lastElement) {
      Intrinsics.checkNotNullParameter($this$lastElement, "$this$lastElement");
      CharSequence var1 = (CharSequence)$this$lastElement;
      boolean var2 = false;
      return var1.length() == 0 ? null : $this$lastElement.charAt($this$lastElement.length() - 1);
   }

   @Nullable
   public static final Character getFirstElement(@NotNull String $this$firstElement) {
      Intrinsics.checkNotNullParameter($this$firstElement, "$this$firstElement");
      CharSequence var1 = (CharSequence)$this$firstElement;
      boolean var2 = false;
      return var1.length() == 0 ? null : $this$firstElement.charAt(0);
   }

   public static final void main() {
      String a = "Hello World";
      Character var1 = lastElement(a);
      boolean var2 = false;
      System.out.println(var1);
      var1 = getFirstElement(a);
      var2 = false;
      System.out.println(var1);
   }
   // $FF: synthetic method
   public static void main(String[] var0) {
      main();
   }
}

从Java代码中我们可以看到,最终也是转成一个静态的getFirstElement方法,其实我们很容易理解,当我们的属性firstElement 设置成val 非私有的时候,对应的Java代码就会默认持有getter方法,也就是我们的getFirstElement。当我们定义了扩展属性,那么对应的Java代码就会转成相应的静态的getter方法。

3、扩展的能力边界?

从以上我们转成Java便可以知道,kotlin中不管的是扩展函数还是扩展属性对应都是Java中的静态方法,也就是主要用于替代Java 中的各种工具类。例如我们的工具类主要是操作字符串,那么我们就对String类进行扩展

扩展能做什么?

在Kotlin中几乎所有的类都可以扩展、主要用途是取代Java中的各种工具类,StringUtils等等。当我们对某个类进行扩展成员的时候,扩展的成员实际上不是真正的成员,但是我们在编译器中有智能的提示,这样一来就方便开发。

扩展不能做什么?

  • 扩展不是真正的成员,无法被子类重写
  • 扩展属性无法存储状态
    如:
//定义扩展属性
val String.firstElement: Char?
    get() = if (isEmpty()) null else this[0]

其实很容易理解,扩展属性转成Java代码其实就对应了静态的getter方法而已,具体的值由getter方法返回值决定

  • 扩展的访问作用域
    (1)定义处的成员
    (2)接受者类型的公开成员
    如:
private val name = "dog";
//定义扩展属性
val String.firstElement: Char?
    get() = if (isEmpty()) {
        null
    } else {
        println(name)
        println(this.length)
        this[0]
    }

name成员虽然是私有的,但是定义在和扩展属性firstElement同一个文件,因此是可以访问到name 的,否则不能
其次就是访问被扩展类型的公开成员。如以上的例子访问String的length 通过this.length,如果length是私有的则不能访问。
以上我们定义的是顶层扩展,如果我们在某个类中进行扩展呢?

class Pig {
    //对String 增加扩展函数lastElement
//直接定义在kotlin文件里,称之为顶层扩展、
    fun String.lastElement(): Char? {
        if (this.isEmpty()) {
            return null
        }
        return this[length - 1]
    }

    private val name = "dog";
    //定义扩展属性
    val String.firstElement: Char?
        get() = if (isEmpty()) {
            null
        } else {
            println(name)
            println(this.length)
            this[0]
        }

    fun testDemo(){
        val a = "Hello World"
        println(a.lastElement())
        println(a.firstElement)
    }
}

转成Java代码


public final class Pig {
   private final String name = "dog";

   @Nullable
   public final Character lastElement(@NotNull String $this$lastElement) {
      Intrinsics.checkParameterIsNotNull($this$lastElement, "$this$lastElement");
      CharSequence var2 = (CharSequence)$this$lastElement;
      boolean var3 = false;
      return var2.length() == 0 ? null : $this$lastElement.charAt($this$lastElement.length() - 1);
   }

   @Nullable
   public final Character getFirstElement(@NotNull String $this$firstElement) {
      Intrinsics.checkParameterIsNotNull($this$firstElement, "$this$firstElement");
      CharSequence var2 = (CharSequence)$this$firstElement;
      boolean var3 = false;
      Character var10000;
      if (var2.length() == 0) {
         var10000 = null;
      } else {
         String var4 = this.name;
         var3 = false;
         System.out.println(var4);
         int var5 = $this$firstElement.length();
         var3 = false;
         System.out.println(var5);
         var10000 = $this$firstElement.charAt(0);
      }

      return var10000;
   }

   public final void testDemo() {
      String a = "Hello World";
      Character var2 = this.lastElement(a);
      boolean var3 = false;
      System.out.println(var2);
      var2 = this.getFirstElement(a);
      var3 = false;
      System.out.println(var2);
   }
}

其实非常容易了理解,当扩展成员定义在类中,那么只能在类中访问
因此可知:
如果是顶层扩展,是可以被全局使用的,扩展成员的访问作用域限于所在文件的所有成员,以及被扩展类型的公开成员
如果是在类中扩展,那么只能在类中使用,扩展成员的访问作用域为该类的成员,以及被扩展类型的公开成员

4、实战与思考

以上我们分析了扩展函数常用于替代Java中的各种工具类,仅此而已吗?
我们来看下Kotlin中的String

public class String : Comparable, CharSequence {
    companion object {}
    
    public override fun get(index: Int): Char

    public override fun subSequence(startIndex: Int, endIndex: Int): CharSequence

    public override fun compareTo(other: String): Int
}

我们看到String中只有几个核心方法,而平时我们使用到的这么多API,都在Strings这个顶层扩展文件中,


image.png

由此可知,在优化我们的项目架构的时候,我们可以将核心方法写在类中,非核心方法我们可以通过扩展的方式实现分离。

第二就是对于SDK中的代码,我们在使用的时候,总是要写很多公共的模板代码,如果我们使用Java可以想到把他封装成一个静态工具类,在Kotlin我们便可以通过扩展成员的方式进行封装。虽然说扩展的方式转换成Java代码也是通过静态方法进行封装,但是对于我们开发中却非常方便,开发中编译器就类似把扩展成员当做真正的成员,智能提示。
如下代码:

当我们要设置View的margin的时候,总要写很多模板代码

    val param = textView?.layoutParams as ViewGroup.MarginLayoutParams
    param.bottomMargin = 10
    param.topMargin = 10
    param.marginStart = 10
    param.marginEnd = 10
    textView.layoutParams = param

那么我们便可以通过扩展的方式,使得代码更加方便

fun View.setMargin(left: Int, top: Int, right: Int, bottom: Int) {
    ( layoutParams as ViewGroup.MarginLayoutParams).let { 
        it.bottomMargin = bottom
        it.topMargin = top
        it.marginStart = left
        it.marginEnd = right
    }
}
fun main() {
   val textView: TextView? = null
    textView?.setMargin(10, 20, 30, 50)

    val button: Button? = null
    button?.setMargin(10, 20, 30, 50)
}

这样针对所有的View我们都可以使用。

5、小结

  • 扩展我们可以用于替代Java中的一些工具类
  • 我们可以将核心方法写在类中,非核心成员写在扩展成员中
  • 当我们调用一些外部SDK中的函数总是要写很多模板方法,我们便可以对SDK类进行扩展
  • 扩展成员不是真正的成员,因此不能继承,不能保存数据,其底层最终也是封装成了静态的方式实现
  • 顶层扩展在任何地方都可以使用,类中扩展只能在类中使用,因此我们一般使用底层扩展

你可能感兴趣的:(Kotlin-扩展(Extension)的能力边界在哪?)