跳转至

函数

函数定义与调用

//定义函数
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()
}
val list = listOf(1,2,3)
println(joinToString(list,";","(",")"))

参数

Kotlin参数是val类型的不可修改。

命名参数

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

 println(joinToString(list,separator =";",prefix = "(",postfix = ")"))

默认参数值

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

1
2
3
4
5
6
fun <T> joinToString(collection:Collection<T>,
                     separator:String = ",",
                     prefix:String = "",
                     postfix:String = ""):String{
   //...
}
1
2
3
4
println(joinToString(list)) // 1,2,3
println(joinToString(list,";")) //1;2;3
println(joinToString(list,";","(",")")) //(1;2;3)
println(joinToString(list,prefix = "(")) //(1,2,3

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

当你从Java中调用Kotlin函数的时候必须显式地指定所有参数值。如果需要从Java代码中做频繁的调用,而且希望它能对Java的调用者更简便,可以用@JvmOverloads注解它。这个指示编译器生成如下重载函数。

扩展函数需要进行导入才能使用它。

可变参数

1
2
3
4
5
6
fun <T> asList(vararg ts: T): List<T> {
    val result = ArrayList<T>()
    for (t in ts) // ts is an Array
        result.add(t)
    return result
}
val list = asList(1, 2, 3)

调用 vararg 函数时,可以单独传递参数,例如 asList(1, 2, 3) 。如果您已经有一个数组并希望将其内容传递给函数,请使用 spread 运算符(在数组前面加上 * ):

val a = intArrayOf(1, 2, 3) // IntArray is a primitive type array
val list = asList(-1, 0, *a.toTypedArray(), 4)

中缀表示法

1
2
3
4
//定义
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)
//调用
val pair = "a" to "b"

顶层函数和属性

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

这里它会编译成如下的Java代码:

1
2
3
4
package strings;
public class JoinKt {
    public static String joinToString(...){...}
}

可以看到Kotlin编译生成的类的名称,对应于包含函数的文件的名称。这个文件中的所有顶层函数编译为这个类的静态函数。

要改变包含Kotlin顶层函数的生成的类的名称,需要为这个文件添加@JvmName的注解,将其放到这个文件的开头,位于包名的前面:

@file:JvmName("StringFunctions")
package strings

顶层属性

const val UNIX_LINE_SEPARATOR = "\n"

扩展函数和属性

添加扩展函数就是把你要扩展的类或者接口的名称,放到即将添加的函数前面。这个类的名称被称为接收者类型;用来调用这个扩展函数的那个对象,叫做接收者对象。

fun String.lastChar(): Char = this.get(this.length-1)
println("Kotlin".lastChar()) // n

在扩展函数中,可以像其他成员函数一样用this。而且也可以像普通的成员函数一样,省略它。

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

在扩展函数中,可以直接访问被扩展的类的其他方法和属性,就好像是在这个类自己的方法中访问它们一样。和在类内部定义的方法不同的是,扩展函数不能访问私有的或者受保护的成员。

3.3.1 导入和扩展函数

1
2
3
import strings.lastChar
//import strings.* //也可以用*来导入
println("Kotlin".lastChar()) // n

可以使用关键字as来修改导入的类或者函数名称,可以避免重名函数的冲突。

import strings.lastChar as last
println("Kotlin".last()) // n

3.3.2 从Java中调用扩展函数

和顶层函数一样,包含这个函数的Java类的名称,是由这个函数声明的文件名称决定的。假设它声明在一个叫做StringUtil.kt的文件中

char c = StringUtilKt.lastChar("Java");

3.3.3 作为扩展函数的工具函数

fun <T> Collection<T>.joinToString(
                     separator:String = ",",
                     prefix:String = "",
                     postfix:String = ""):String{

    val result = StringBuilder(prefix)
    for ((index,element) in withIndex()){
        if(index>0) result.append(separator)
        result.append(element)
    }
    result.append(postfix)
    return result.toString()
}
val list = listOf(1,2,3)
println(list.joinToString(separator = ";",prefix = "(",postfix = ")")) //(1;2;3)

扩展函数的静态性质也决定了扩展函数不能被子类重写。

3.3.4 不可重写的扩展函数

1
2
3
open class View {
    open fun click() = println("View clicked")
}
1
2
3
open class Button:View() {
    override fun click() = println("Button clicked")
}
val view:View = Button()
view.click() //Button clicked
1
2
3
4
fun View.showoff() = println("I'm a view!")
fun Button.showoff() = println("I'm a button!")
val view:View = Button()
view.showoff() //I'm a view!

3.3.4 扩展属性

val String.lastChar: Char
    get() = get(length - 1)
1
2
3
4
println("Kotlin".lastChar) //n
val sb = StringBuilder("Kotlin?") 
sb.lastChar = '!'
println(sb) //Kotlin!

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

3.4.1 扩展Java集合的API

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

Kotlin可变参数是在参数上使用vararg修饰符。

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

Java中可以按原样传递数组,而kotlin则要求你显式地解包数组,以便每个数组元素在函数中能作为单独的参数来调用。从技术的角度来讲,这个功能被称为展开运算符

1
2
3
4
fun main(args: Array<String>) {
    val list = listOf("args:",*args)
    println(list) //[args:, one, two, three]
}

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

 val map = hashMapOf(1 to "one", 7 to "seven", 53 to "fifty-three")

这行代码中的单词to不是内置的结构,而是一种特殊的函数调用,被称为中缀调用。

在中缀调用中,没有添加额外的分隔符,函数名称是直接放在目标对象名称和参数之间的。以下两种调用方式是等价的:

1.to("one")
1 to "one"

中缀调用可以与只有一个参数的函数一起使用,无论是普通的函数还是扩展函数。要允许使用中缀符号调用函数,需要使用infix修饰符来标记它。

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

可以直接用Pair的内容来初始化两个变量:

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

这个功能称为解构声明。用to函数创建一个pair,然后用解构声明来展开。

3.5 字符串和正则表达式的处理

3.5.1 分割字符串

String[] strings="12.345-6.A".split(".");
System.out.println(strings.length); //0

Java的split方法将一个正则表达式作为参数,并根据表达式将字符串分割成多个字符串。这里点(.)是表示任何字符的正则表达式。

Kotlin把这个令人费解的函数隐藏了,作为替换,提供了一些名为split的,具有不同参数的重载的扩展函数。用来承载正则表达式的值需要一个Regex类型,而不是String,这样确保了当有一个字符串传递给这些函数的时候,不会被当做正则表达式。

println("12.345-6.A".split("\\.|-".toRegex())) //[12, 345, 6, A]

Kotlin中的split扩展函数的其他重载支持任意数量的纯文本字符串分隔符:

println("12.345-6.A".split(".","-")) //[12, 345, 6, A]

3.5.2 正则表达式和三重引号的字符串

1
2
3
4
5
6
7
fun parsePath(path: String) {
    val directory = path.substringBeforeLast("/")
    val fullName = path.substringAfterLast("/")
    val fileName = fullName.substringBeforeLast(".")
    val extension = fullName.substringAfterLast(".")
    println("Dir: $directory, name: $fileName, ext: $extension")
}

3.5.3 多行三重引号的字符串

3.6 让你的代码更简洁:局部函数和扩展

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

fun saveUser(user:User){
    //重复的字段检查
    if(user.name.isEmpty()){
        throw IllegalArgumentException(
                "Can't save user ${user.id}:empty Name")
    }
    if(user.address.isEmpty()){
        throw IllegalArgumentException(
                "Can't save user ${user.id}:empty Address")
    }
    //保存user到数据库
}
fun saveUser(user: User) {
    fun validate(user: User,value: String, fieldName: String){
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                    "Can't save user ${user.id}:empty $fieldName")
        }
    }
    validate(user,user.name,"Name")
    validate(user,user.address,"Address")
    //保存user到数据库
}

因为局部函数可以访问所在函数中的所有参数和变量。我们可以利用这一点,去冗余的User参数。

fun saveUser(user: User) {
    fun validate(value: String, fieldName: String){
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                    "Can't save user ${user.id}:empty $fieldName")
        }
    }
    validate(user.name,"Name")
    validate(user.address,"Address")
    //保存user到数据库
}
fun User.validateBeforeSave(){
    fun validate(value: String, fieldName: String){
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                    "Can't save user $id:empty $fieldName")
        }
    }
    validate(name,"Name")
    validate(address,"Address")
}
fun saveUser(user: User) {
    user.validateBeforeSave()
    //保存user到数据库
}