Kotlin学习之路(3)——函数的定义与调用

函数的定义与调用

让函数更好调用

命名参数

使用Java这种面向对象函数,我们肯定会经常遇到函数调用,例如:

joinToString(list, "; ", "[", "]")

当我们第一次调用这种函数时就很懵,不知道每一个参数的含义,就造成了不必要的麻烦。

当调用一个Kotlin定义的函数时,可以显示的表明参数的名称。 如果在调用一个函数时你指明了某一个参数的名称,那么为了避免混淆,你需要指明所有i的参数名称,例如:

joinToString(list, separator = "; ", prefix = "[", postfix = "]")

默认参数值

Java中还存在有一个问题,就是有些类的重载方法太多了,就导致我们不得不了解每一个参数的含义,知道每个API的含义。

Kotlin中,可依在声明函数的时候,指定参数的默认值,这样就避免创建重载的函数。

fun joinToString(
    collection: Collection<*>,
    separator: String = ", ",	//默认参数值为","
    prefix: String = "[",		//默认参数值为“["
    postfix: String = "]"		//默认参数值为”]“
): String {...}

那么我们就可以如下调用该函数:

//常规调用函数
joinToString(list
joinToString(list, ",", "", "")
joinToString(list, ";")
//使用命名参数形式调用函数
joinToString(list, separator = "; ", prefix = "[", postfix = "]")
joinToString(list, postfix = "}", prefix = "{")

如上述不同的调用,我们做出以下总结:

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

顶层函数和属性

使用Java开发时,我们肯定定义过一些工具类,这里面包含了一些静态工具方法供我们调用,例如:

public class Util{
	public static String joinToString(...){
		...
	}
}

使用Kotlin就不必这么麻烦,可以直接把这个函数放在代码文件的顶层作为顶层函数就可以了,不用属于任何的类。

pacakage secondUit

fun joinToString(...): String{...}

顶层属性就和顶层函数非常相似,只不过就是由一个方法变为一个属性罢了。

顶层属性,我理解大致可以分为三种:

  1. var修饰的顶层属性,表示可变属性,拥有getter和setter访问器,对应Java中的可变static变量。
  2. val修饰的顶层属性,表示仅可读属性,只拥有getter访问器,对应Java中的不可变static变量。
  3. const val修饰的顶层属性,表示仅可读属性,只拥有getter访问器,对应Java中的static final变量。

扩展方法和属性

正如其名,扩展函数就是为一个接受类新添加一个方法,例如:

fun String.lastChar(): Char = this.get(this.length - 1)

上述代码就是为String类添加一个方法lastChar(),这个函数的意义就是获取这个字符串的最后一个字符。

下面用文字描述创建扩展函数格式:

关键字fun 接受类.扩展函数名: 扩展函数返回类型 = ...

调用扩展函数就像类的普通成员方法一样:

println("Kotlin".lastChar())	//打印n

在扩展函数中,可以直接访问被扩展类的其他方法和属性,就好像是在这个类中的方法访问它们一样。

但是,扩展函数并不能打破被扩展类的封装性,也就是说扩展方法不能访问私有或者是受保护的成员。

在Kotlin中,扩展函数不允许被重写,因为Kotlin会把它当作静态函数对待。

扩展属性和扩展方法特别类似,我们直接举例把:

val String.lastChar: Char = 
	get() = get(length - 1)
var StringBuilder.lastChar: Char
        get() = get(length - 1)
        set(value) {
                this.setCharAt(length - 1 , value)
        }

上述分别定义了不可变和可变的扩展属性,接下来我们尝试访问它们:

println("Kotlin".lastChar) 	//打印n

val sb = StringBuilder("Kotlin?")
sb.lastChar = '!'
println(sb)		//打印Kotlin!

处理集合:可变参数,中缀调用和库的支持

扩展Java中集合的API

Kotlin中的集合其实就是Java中的集合,只是对API做了一个扩展。

val stringSet: List<String> = listOf("one", "two", "fourteenth")
println(stringSet.last())		//打印fourteenth

val numberSet: Collection<Int> = setOf(1, 5, 63)
println(numberSet.maxOrNull())		//打印63

这里的last()函数或者是maxOrNull()函数都被声明称了扩展函数。

可变参数:让函数支持任意数量的参数

还记得我们用kotlin创建集合使用的函数嘛?

val list = listOf(1, 2, 63, 33)

看一下listOf这个函数的具体声明:

public fun <T> listOf(vararg elements: T): List<T> = ...

我们可以看见vararg修饰符修饰了参数elements,这说明了这个参数为可变参数,与Java的区别就是修饰符不同,替代了…修饰符

Kotlin与Java还有一个区别,当需要传递的参数已经包装在某一个数组中,调用该函数的语法不同。

在Java中我们可以按原样传递数组,但是Kotlin需要我们显式地展开数组,以便数组中的每个元素都被当作单独的元素调用。我们称之为展开运算符,也就是*运算符

fun main(args: Array<String>){
    val list = listOf("args: ", *args)
    println(list)
}

键值对的处理:中缀调用和解构声明

创建map集合时,我们使用mapOf函数来创建:

val numberMap = mapOf(1 to "one", 2 to "two", 14 to "fourteen")

其实这里的to,也是一种特殊函数的调用,我们称之为中缀调用

这里的1 to “one”用法就相当于1.to(“one”)

我们看一下简单的to函数的声明:

infix fun Any.to(other: Any) = Pair(this,other)

这里使用infix修饰符标记是因为我们需要使用中缀符号to来调用函数。

可以看出,to函数会返回Pair类型的对象,Pair时Kotlin中标准库的类,他表示一对元素。

而解构声明是什么呢?

val (number, name) = 1 to "one"

就是用to函数创建了一个pair,我们可以用解构声明展开。

除此之外,我们的map(key, value),还有使用集合类的withIndex函数都可以使用解构声明。

局部函数和扩展

我们要在保证功能完整地前提下尽可能地将自己的代码写的简洁。

假如我们有一个用户类,我们需要经过一些判断名字和地址非空后保存到数据库中:

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User){
    if(user.name.isEmpty()){
        throw IllegalAccessException("Can't save user ${user.id}: Empty name!")
    }
    if (user.address.isEmpty()){
        throw IllegalAccessException("Can't save user ${user.id}: Empty address!")
    }
        //保存到数据库的逻辑(未写)
}

我们会发现上述的两个判空我们可以提取出来,优化代码:

fun saveUser(user: User){ 
    //局部函数可以直接访问外部函数的参数
    fun validate(value:String, fileName: String){
        if(value.isEmpty()){
            throw IllegalAccessException("Can't save user ${user.id}: Empty $fileName!")
        }
    }

    validate(user.name,"name")
    validate(user.address, "address!")

    //保存逻辑(未写)
}

从上述代码我们可以看到,局部函数可以访问所在函数中的所有参数和变量

当然我们也可以运用前面所学的扩展函数进行更加一步的优化:

fun User.validateBeforeSave(){
    fun validate(value:String, fileName: String){
        if(value.isEmpty()){
            throw IllegalAccessException("Can't save user $id: Empty $fileName!")
        }
    }

    validate(name,"name")
    validate(address, "address!")
}

fun saveUser(user: User){ 
	user.validateBeforeSave()
	//保存逻辑(未写)
}
上一篇:程序员为什么越老贬值的越厉害?,androidapk性能优化


下一篇:kotlin第二阶段