3.2让调用函数更简单

已经讲解过如何创建一个集合,那再来做些别的:打印它的内容:
感觉很简单,然而其中有一堆的概念。Java的集合都有一个默认的toString实现,但是它格式化的输出都是固定的,而且往往不是需要的样子:

val list = listOf(1, 2, 3)
>>>println(list)
>[1,2,3]

假设需要用一个分号来分隔每一个元素,然后用括号括起来,而不是采用默认实现的方括号:(1;2;3)。要解决这个问题,Java项目会使用一些第三方库,比如Guava或者Apache Commons,或者是在项目中重写打印的函数。在Kotlin中,它的标准库中有一个专门的函数来处理这种情况。
本节不借助Kotlin的工具来简化函数声明,从直接重写实现函数开始,然后再过渡到Kotlin更惯用的方法来重写。
下面的joinToString函数就展现了通过在元素中间添加分割符号,在最前面添加后缀,在最末尾添加后缀的方式把集合的元素逐个添加到一个StringBuilder的过程。

fun <T> joinToString(collection: Collection<T>, separator: String, prefix: String, postfix: String): String {
        val result = StringBuilder(prefix)
        for ((index, element) in collection.withIndex()) {
            //第一个元素前不需要添加分割符
            if (index > 0) result.append(separator)
            result.append(element)
        }
        result.append(postfix)
        return result.toString()
    }

这个函数是泛型:它可以支持元素为任意类型的集合。这里泛型的语法和Java类似。
验证一下这个函数运行起来是不是和设想一样:

val list = listOf(1, 2, 3)
println(joinToString(list, "; ", "(", ")"))
(1;2;3)

看来可行。接下来,会聚焦到它的声明:要怎样修改,让这个函数的调用更加简洁呢?也许我们可以避免每次调用的时候,都传入4个参数。

3.2.1命名参数

我们要关注的第一个问题就是函数的可读性。举个栗子,来看看joinToString的调用:

joinToString(collection, " ", " ", ".")

能看出这些String都对应的是什么参数吗?这个集合的元素是用空格还是点号来分割?如果不去查看函数的声明,很难回答这些问题。对于Boolean类型的标志,这个问题尤其明显。为解决这个问题,一些Java编程风格,推荐创建enum类型而不是采用Boolean;而另外一些风格,会要求你通过添加注释,在注释中指明参数的类型,例如:

joinToString(collection, /* separator */ " ", /* prefix */ " ", /* postfix */ ".")

在Kotlin中,可以做得更优雅:

joinToString(collection,  separator = " ", prefix = " ", postfix = ".")

当调用一个Kotlin定义的函数时,可以显式地标明一些参数的名称。如果在调用一个函数时,指明了一个参数的名称,为了避免混淆,那它之后的所有参数都需要标明名称。

3.2.2默认参数值

Java的另一个普遍存在的问题是,一些类的重载函数实在太多了。只要看一眼java.lang.Thread以及它对应的8个构造方法,头就大了。这些重载原本是为了向后兼容,方便这些api的使用者,又或者出于别的原因,但导致的最终结果是一致的:重复。这些参数名和类型被重复了一遍又一遍,如果你是一个好码农,还必须在每次重载的时候重复大部分的文档。与此同时,当你调用一个省略了部分参数的重载函数时,你可能会搞不懂它们到底用的是哪个。
在Kotlin中,可以在声明函数的时候,指定参数的默认值,这样就可以避免创建重载的函数。改进前面的joinToString函数。在大多数情况下,字符串可以不加前缀或者后缀并用逗号分隔。所以,我们把这些设置为默认值:

fun <T> joinToString(collection: Collection<T>, separator: String = ", ", prefix: String = "", postfix: String = ""): String {
        val result = StringBuilder(prefix)
        for ((index, element) in collection.withIndex()) {
            //第一个元素前不需要添加分割符
            if (index > 0) result.append(separator)
            result.append(element)
        }
        result.append(postfix)
        return result.toString()
    }

现在可以调用所有参数来调用这个函数,或者省略部分参数:

println(joinToString(list, ", ", "", ""))
>1,2,3
println(joinToString(list))
>1,2,3
println(joinToString(list, "; "))
>1;2;3

当使用常规的调用语法时,必须按照函数声明中定义的参数顺序来给定参数,可以省略的只有排在末尾的参数。如果使用命名参数,可以省略中间的一些参数,也可以以你想要的任意顺序只给定需要的参数:

 println(joinToString(list, postfix = ";", prefix = "# "))
 ># 1, 2, 3;

注意,参数的默认值是被编码到被调用的函数中,而不是调用的地方。如果你改变了参数的默认值并重新编译这个函数,没有给参数重新赋值的调用者,将会使用新的默认值。

3.2.3消除静态工具类:顶层函数和属性

Java作为一门面向对象的语言,需要所有的代码都写作类的函数。大多数情况下,这种方式还能行得通。但事实上,几乎所有的大型项目,最终都有很多的代码并不能归属到任何一个类中。有时一个操作对应两个不同的类的对象,而且重要性相差无几。有时存在一个基本的对象,但不想通过实例函数来添加操作,让它的api继续膨胀。
结果就是,最终这些类将不包含任何的状态或者实例函数,而是仅仅作为一堆静态函数的容器。在JDK中,最适合的例子应该就是Collections了。看看现在想项目的代码,是不是也有一些类本身就是已Util作为后缀名。
在Kotlin中,根本不需要去创建这些无意义的类。相反,可以把这些函数直接放到代码文件的顶层,不用从属于任何的类。这些放在文件顶层的函数依然是包内的成员。如果需要从包外访问,则需要import,但不再需要额外包一层。
来把joinToString直接放到strings的包中试一下。

package strings

import java.lang.StringBuilder


/**
 * @author :Reginer in  2018/5/15 22:21.
 * 联系方式:QQ:282921012
 * 功能描述:
 */
class Aa {
    fun <T> joinToString(collection: Collection<T>, separator: String = ", ", prefix: String = "", postfix: String = ""): String {
        val result = StringBuilder(prefix)
        for ((index, element) in collection.withIndex()) {
            //第一个元素前不需要添加分割符
            if (index > 0) result.append(separator)
            result.append(element)
        }
        result.append(postfix)
        return result.toString()
    }
}

可以看到Kotlin编译生成的类的名称,对应于包含函数的文件的名称。这个文件中的所有顶层函数编译为这个类的静态函数。因此,当从Java调用这个函数的时候,和调用任何其他静态函数一样简单:

Aa.joinToString(list, ", ", "", "");

顶层属性

和函数一样,属性也可以放到文件的顶层。在一个类的外面保存单独的数据片段虽然不常用,但还是有它的价值。
可以在代码中用顶层属性来定义常量:

val UNIX_LINE_SEPARATOR = "\n"

默认情况下,顶层属性和其他任意的属性一样,是通过访问器暴露给Java使用的(getter,setter)。为了方便使用,如果想把一个常量以 public static final的属性暴露给Java,可以用const来修饰它(适用于所有基本类型的属性,以及String类型)

const val UNIX_LINE_SEPARATOR = "\n"

等同于下面的Java代码:

 public static final UNIX_LINE_SEPARATOR = "\n"

猜你喜欢

转载自blog.csdn.net/qq_26413249/article/details/80398346
今日推荐