函数的定义与调用
让函数更好调用
命名参数
使用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 = "{")
如上述不同的调用,我们做出以下总结:
- 使用常规调用语法时,必须按照函数声明中定义的参数顺序来给定参数,可以省略的只有排在末尾的参数。
- 使用命名参数调用函数时,可以省略中间的一些参数,也可以以任意顺序给定你需要的参数。
顶层函数和属性
使用Java开发时,我们肯定定义过一些工具类,这里面包含了一些静态工具方法供我们调用,例如:
public class Util{
public static String joinToString(...){
...
}
}
使用Kotlin就不必这么麻烦,可以直接把这个函数放在代码文件的顶层作为顶层函数就可以了,不用属于任何的类。
pacakage secondUit
fun joinToString(...): String{...}
顶层属性就和顶层函数非常相似,只不过就是由一个方法变为一个属性罢了。
顶层属性,我理解大致可以分为三种:
- var修饰的顶层属性,表示可变属性,拥有getter和setter访问器,对应Java中的可变static变量。
- val修饰的顶层属性,表示仅可读属性,只拥有getter访问器,对应Java中的不可变static变量。
- 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()
//保存逻辑(未写)
}