Kotlin 中的扩展函数和扩展属性

Kotlin 扩展函数

在 android 开发中,经常会使用到各种 utils 工具类。比如 ViewUtils, ScreenUtils, FileUtils 等,

这些类往往会提供一些静态方法,让我们进行一些通用的操作。

或者,我们常常会希望给某一个类增加一两个小属性或者方法,在用到这个类的地方都能调用,方便我们的开发。比如,我们希望每个 String 对象都能有一个方法,返回这个 String 对象包含的大写字母个数。

我们可能会在某个 utils 中这样写

public static int getStringCapitalCount(String s) throws Exception {
        if (s == null) {
            throw new Exception("string should not be null!");
        }
        int count = 0;
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c >= 65 && c <= 90) {
                count++;
            }
        }
        return count;
    }

或者, 我们会考虑使用一个子类来继承这个父类,然后给子类添加这个方法,在代码中使用子类。当然,String 是不可继承的。

这样写,并没有什么问题,一贯的 java 写法。

但是在 Kotlin 中,我们有了更优雅的方式。既然我们最开始的目标是希望 String 类型多一个方法,那么我们的代码也应该写得像这样,如下:

// 函数名以类名作为前缀: ClassName.funName
// 该前缀叫做 接收者类型(receiver type)
fun String.capitalCount(): Int{ 
    var count = 0
    this.forEach { // this 指的是类的实例对象, 叫 接收者对象(receiver object)
        if (it.toInt() in 65..90) {
            count ++
        }
    }
    return count
}

然后,我们可以在任何使用到 String 的地方,调用这个方法,就好像 String 类型本来就拥有这个方法一样。

fun main() {
    val s = "AbCdEfg"
    println(s.capitalCount())
}

这就是 Kotlin 中的扩展函数。

一般,将扩展函数定义在一个 Extensions.kt 的文件中,最外层直接写函数就可以,不需要像 java 一样定义一个类。这样定义的函数是顶层函数,类似于 java 中的静态函数,可以在任何地方被调用。

image-20200426114707470.png

类似地, 我们再写一个例子,比如经常用到的将 Date 转为 String 的函数,我们使用扩展函数的方式:

fun Date.dateString(): String{
    val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
    return dateFormat.format(this)
}

// 调用时
fun main() {
    println(Date().dateString())
}

注意

  • 扩展函数虽然看起来像是往类里面添加了成员函数,但是实际上并没有改变类的任何结构。所以,对于类内部 private、protected 以及 internal(模块外访问)修饰的成员变量和函数,是不能够访问到的,编译器会报错。

    Encapsulation of an Extension Function in Kotlin
  • 如果扩展函数和类内部定义的成员函数重名,那么调用时执行的是类的函数。

  • 扩展是静态解析的,调⽤的扩展函数是由函数调⽤所在的表达式的类型来决定的,⽽不是由表达式运⾏时求值结果决定的。例如:

open class Shape
class Rectangle: Shape()
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())

这个例子会输出 “Shape” ,因为调用的扩展函数只取决于 s 参数的类型声明,这里它是 Shape 类。

Kotlin 扩展属性

和扩展函数类似,Kotlin 同样支持扩展属性。比如:

// 给 StringBuilder 类添加扩展属性 firstLetter
var StringBuilder.firstLetter: Char
    get() = get(0)
    set(value) = this.setCharAt(0, value)

可以把扩展属性看作是两个扩展函数,一个是 getter, 一个是 setter 。

调用时:

fun main(args: Array) {
    val message = StringBuilder("hello !")
    println("${message.firstLetter} is the first letter of $message")
    message.firstLetter = 'H'
    println("${message.firstLetter} is the first letter of $message")
}

输出结果:

h is the first letter of hello !
H is the first letter of Hello !

注意

扩展属性没有初始化器,即扩展属性不能初始化,只能使用 getter / setter 来操作扩展属性的值。

val StringBuilder.firsetLetter = 'a' // 这是错误的, 不能初始化扩展属性

这是因为扩展属性并没有真正地插入一个属性到类中,所以扩展属性没有幕后字段(backing field),不能进行初始化。同样,也不能在扩展属性的 setter 里使用 field 关键字。

配合泛型使用

可以使用泛型来给多个类添加扩展属性。比如:

// 得到逆序的 string
val  T.reverseString: String
    get() {
        return this.toString().reversed() // 实际上 reversed 函数也是 Kotlin String 类型自带的扩展函数
    }

扩展的 Java 代码

Kotlin 究竟是如何实现扩展函数和扩展属性的呢?

还是使用之前 String.capitalCount 的例子,反编译后的 java 代码如下:

// Extension.kt 文件会被编译成 ExtensionKt.class
public final class ExtensionsKt {
   public static final int capitalCount(@NotNull String $this$capitalCount) {
      int count = 0;
      CharSequence $this$forEach$iv = (CharSequence)$this$capitalCount;
      int $i$f$forEach = false;
      CharSequence var4 = $this$forEach$iv;

      for(int var5 = 0; var5 < var4.length(); ++var5) {
         char element$iv = var4.charAt(var5);
         int var8 = false;
         if ('A' <= element$iv) {
            if ('Z' >= element$iv) {
               ++count;
            }
         }
      }
 return count;
}

实际上和在 java util 中实现方法一致,生成了一个以 String 类型为形参的静态方法,而函数形参 this$capitalCount 其实就是在 kotlin 中 this 指代的那个对象。

再看 StringBuilder.firstLetter 属性的反编译 java 代码:

public static final char getFirstLetter(@NotNull StringBuilder $this$firstLetter) {
      return $this$firstLetter.charAt(0);
   }

public static final void setFirstLetter(@NotNull StringBuilder $this$firstLetter, char value) {
      $this$firstLetter.setCharAt(0, value);
   }

其实就是生成了两个静态的方法,一个 setter 和 一个 getter。用两个扩展函数也可以达到同样的效果。

你可能感兴趣的:(Kotlin 中的扩展函数和扩展属性)