《Kotlin 项目实战教程》

第6章 扩展函数与属性

在使用Java的时候,我们经常使用诸如StringUtil, DateUtil等等一堆工具类,代码写起来也比较冗长。举个例子,获取一个字符串的第一个字符值、最后一个字符值。如果我们用Java代码来写,通常是要先声明一个StringUtil类,然后在里面写相应的工具方法,代码可以是下面的这个样子

package com.easy.kotlin;

import static java.lang.System.out;

public class StringUtil {

    /**
     * 获取str的第一个字符值
     *
     * @param str
     * @return
     */
    public static String firstChar(String str) {
        if (str != null && str.length() > 0) {
            return str.charAt(0) + "";
        }
        return "";
    }

    /**
     * 获取str的最后一个字符值
     *
     * @param str
     * @return
     */
    public static String lastChar(String str) {
        if (str != null && str.length() > 0) {
            return str.charAt(str.length() - 1) + "";
        }
        return "";
    }

    public static void main(String[] args) {
        String str = "abc";
        out.println(StringUtil.firstChar(str)); // a
        out.println(StringUtil.lastChar(str)); // c
    }
}

我们可以看到 StringUtil.firstChar(str) 这样的调用方式不够简单直接。能不能直接这样调用

"abc".firstChar()
"abc".lastChar()

非常遗憾的是,在Java中我们无法给String类添加一个自定义方法。因为String类是JDK中内置的基础类,而且为final,不能修改。所以,Java程序员通常使用这样的一个变通的方法:开发一个StringUtil类,在里面封装所需要的String操作的方法。而不是修改或继承String类。

而情况到了Kotlin里面,就完全不一样了——我们完全可以自由扩展任何类的方法和属性。在不修改原类的情况下, Kotlin能给一个类扩展新功能而无需继承该类。

本章我们介绍Kotlin的扩展函数和属性。

6.1 扩展函数

Kotlin中提供了非常简单使用的扩展函数功能。我们可以为现有的类自由添加自定义的函数 。

6.1.1 给String类扩展两个函数

例如,我们现在给String类扩展两个函数:lastChar() 和 firstChar() , 实现代码如下

package com.easy.kotlin

fun String.lastChar(): String {
    if (this.length == 0) {
        return ""
    }
    return this[this.length - 1].toString()
}

fun String.firstChar(): String {
    if (this.length == 0) {
        return ""
    }
    return this[0].toString()
}

扩展函数的语法,我们用下图来简单说明

扩展函数的语法

然后,我们就可以在代码中直接调用了

fun main(args: Array<String>) {
    val str = "abc"
    println(str.firstChar()) // a
    println(str.lastChar()) // c
}

如果在其他package路径下面,需要 import 导入扩展函数

package com.easy.kotlin.tutorial // 跟扩展函数不在同一个包路径下面

import com.easy.kotlin.firstChar // 导入扩展函数 firstChar
import com.easy.kotlin.lastChar// 导入扩展函数 lastChar

fun main(args: Array<String>) {
    val str = "abc"
    str.firstChar() // 这样的调用方式要比 StringUtil.firstChar(str) 简单优雅许多
    str.lastChar() 
}

6.1.2 给 List 类扩展一个过滤函数

在上一章中我们介绍过List的filter函数。那么这个 filter 函数是怎样实现的呢? 如果我们自己来给List类扩展一个过滤函数,应该怎样去做呢?下面我们就来介绍。

为了更加深刻体会到 Kotlin 扩展功能的简单优雅性,我们先来看看在Java中是怎样实现的吧!首先,我们会去声明一个ListUtil 类,里面实现一个 List<T> filter(List<T> list, Predicate<T> p) 方法,代码如下


public class ListUtil<T> {
  /**
   * 根据谓词p 过滤 list 中的元素
   *
   * @param list
   * @param p
   * @return
   */
  public List<T> filter(List<T> list, Predicate<T> p) {
      List<T> result = new ArrayList<>();
      for (T t : list) {
          if (p.predicate(t)) {
              result.add(t);
          }
      }
      return result;
  }
}

其中,Predicate 接口声明如下

interface Predicate<T> {
    Boolean predicate(T t);
}

然后,我们在代码中这样使用这个 filter 方法

    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(new Integer[] {1, 2, 3, 4, 5, 6, 7});
        ListUtil<Integer> listUtil = new ListUtil(); // 声明ListUtil对象
        List<Integer> result = listUtil.filter(list, (it) -> it % 2 == 1); // lambda 表达式
        out.println(result); // [1, 3, 5, 7]
    }

为了调用 filter 方法,我们还要声明一个ListUtil对象,费事。能不能直接像下面这样调用?

list.filter { it % 2 == 1 }

答案是肯定的,只不过是在Kotlin中,而不是在Java中。

我们就来使用Kotlin的扩展函数为List扩展一个filter函数,代码如下

fun <T> List<T>.filter(predicate: (T) -> Boolean): MutableList<T> {
    val result = ArrayList<T>()
    this.forEach {
        if (predicate(it)) {
            result.add(it)
        }
    }
    return result
}

这个函数的签名稍微有点复杂,我们用下图来形象化地简单说明

给List扩展的filter函数签名

然后,我们在代码中只需要这样调用即可

    val list = mutableListOf(1, 2, 3, 4, 5, 6, 7)
    val result = list.filter {
        it % 2 == 1
    }
    println(result) // [1, 3, 5, 7]

Kotlin的标准库API中使用扩展的功能,通过扩展Java的API,提供了大量的实用简单的函数。我们将在后面的关于文件IO、正则表达式、多线程等相关主题中介绍。

6.2 扩展属性

除了扩展一个类的函数,我们还可以扩展类属性。例如,我们给 MutableList 扩展两个属性:firstElement 和 lastElement , 实现代码如下


var <T> MutableList<T>.firstElement: T
    get() {
        return this[0]
    }
    set(value) {
        this[0] = value
    }

var <T> MutableList<T>.lastElement: T
    get() {
        return this[this.size - 1]
    }
    set(value) {
        this[this.size - 1] = value
    }

上面的代码中的扩展属性的语法说明如下图所示

扩展属性的语法

然后,我们就可以在代码中直接使用扩展的属性了

    val list = mutableListOf(1, 2, 3, 4, 5, 6, 7)

    println("list = ${list}") // list = [1, 2, 3, 4, 5, 6, 7]
    println(list.firstElement) // 调用getter 函数 , 值是 1
    println(list.lastElement) // 7

    list.firstElement = -1 // 调用 setter 函数
    list.lastElement = -7

    println("list = ${list}") // list = [-1, 2, 3, 4, 5, 6, -7]
    println(list.firstElement) // -1
    println(list.lastElement) // -7

扩展属性允许定义在类或者kotlin文件中,不允许定义在函数中。

6.3 扩展中的this关键字

我们有注意到,在上面的List的扩展函数filter的实现中,我们有用到一个this关键字

    this.forEach {
        if (predicate(it)) {
            result.add(it)
        }
    }

这里的 this 指的是 接收者对象(receiver object), 也就是调用扩展函数时, 在点号( . )之前指定的对象实例。

本章小结


陈光剑
499 声望183 粉丝