Kotlin的函数

623 阅读6分钟

朝阳

1.Kotlin中创建集合

Kotlin没有采用自己的集合类,因为使用标准的Java集合类,Kotlin可以更容易与Java代码交互

val set = hashSetOf("渣渣灰","古天乐","刘青云")
val list = arrayListOf("enjoy","your","time")
val map = hashMapOf(1 to "one",2 to "two",3 to "three")
>>> println(set.javaClass)
class java.util.HashSet
>>> println(list.javaClass)
class java.util.ArrayList
>>> println(map.javaClass)
class java.util.HashMap

但竟然会发现还可以这样写

val strings = listOf("first","second","third")
>>> println(strings.last()) //这个函数其实是扩展的,下文会有介绍
third

2.扩展函数和属性

扩展函数很简单,它就是类的一个成员函数,只不过在类的外边

package strings

/**
 * 该扩展函数来计算字符串的最后一个字符
 */
fun String.lastChar(): Char = this.get(this.length - 1)

String.lastChar()

1.使用扩展函数

import strings.lastChar
val c = "Kotlin".lastChar()
//或者使用*
import strings.lastChar
val ch = "Kotlin".lastChar()
//再或者
import strings.lastChar as last
val c = "Kotlin".last()

Java中调用Kotlin的扩展函数,假定它声明在一个叫做StringUtil.kt的文件中

/* Java */
char c = StringUtilKt.lastChar("Java")

这个扩展函数被声明在顶层函数,所以它会被编译成一个静态函数,Java中直接静态导入就行了
再来扩展一个函数作为工具函数使用

/**
 * 格式化时间,输入为秒
 */
fun durationFormat(duration: Long): String {
    val minute = duration / 60
    val second = duration % 60
    return if (minute <= 9) {
        if (second <= 9) {
            "0$minute' 0$second''"
        } else {
            "0$minute' $second''"
        }
    } else {
        if (second <= 9) {
            "$minute' 0$second''"
        } else {
            "$minute' $second"
        }
    }
}
>>> println(durationFormat(189))
03'09''
>>> println(durationFormat(15))
00'15''

扩展函数就是静态函数的一个高效语法糖

2.不可重写的扩展函数

open class View {
    open fun click() = println("View clicked")
}
class Button: View() {
    override fun click() = println("Button clicked")
}
>>> val view: View = Button()
>>> view.click()
Button clicked

上面这段代码的输出和Java一致,但对于扩展函数来说就不是了

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!

怎么会出现这种呢,可能与想象不太一样,那看下反编译成.class的内容

KotlinByte
原来它的呈现形式是静态且final的,自然不能被重写,在Java中应该这样调用扩展函数

>>> View view = new Button()
>>> ExtensionKt.showOff(view)
I'm a view!

3.扩展属性

和扩展函数一样,扩展属性也像接收者的一个普通成员属性一样

val String.lastChar: Char
        get() = this.get(this.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中使用扩展属性的时候,应该显式的使用getter:

StringUtil.getLastChar("渣渣灰");

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

1.可变参数

val actors = listOf("周星星","fa哥","王祖贤")

很明显,这个初始化list的时候参数个数是可变的,来看下Kotlin的库函数是如何实现的

/**
 * Returns a new read-only list of given elements.  The returned list is serializable (JVM).
 * @sample samples.collections.Collections.Lists.readOnlyList
 */
public fun <T> listOf(vararg elements: T): List<T> = if (elements.size > 0) elements.asList() else emptyList()

使用到了泛型,泛型跟Java的泛型类似,注释中也提到返回的list只读且是序列化的,怎么让它长度可变呢,看下边的

val actors1 = mutableListOf<String>()
actors1.add("杜sir")
val actors2 = arrayListOf<String>() //其实就是Java中的ArrayList
actors2.add("肥猫")

看下Kotlin中这两个函数是如何实现的

/**
 * Returns an empty new [MutableList].
 * @sample samples.collections.Collections.Lists.emptyMutableList
 */
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> mutableListOf(): MutableList<T> = ArrayList()

/**
 * Returns a new [MutableList] with the given elements.
 * @sample samples.collections.Collections.Lists.mutableList
 */
public fun <T> mutableListOf(vararg elements: T): MutableList<T>
        = if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))

/**
 * Returns an empty new [ArrayList].
 * @sample samples.collections.Collections.Lists.emptyArrayList
 */
@SinceKotlin("1.1")
@kotlin.internal.InlineOnly
public inline fun <T> arrayListOf(): ArrayList<T> = ArrayList()

/**
 * Returns a new [ArrayList] with the given elements.
 * @sample samples.collections.Collections.Lists.arrayList
 */
public fun <T> arrayListOf(vararg elements: T): ArrayList<T>
        = if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))

可以看出其实最终都是使用了Java集合的ArrayList()

2.中缀调用

使用mapOf函数创建map:

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

这行代码的to不是内置的结构,而是一种特殊的函数调用,被称为中缀调用,一下两种方式是等价的:

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

要使用中缀符号调用函数,需要使用infix修饰符来标记它,下面是一个简单的to函数的声明:

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

to函数会返回一个Pair类型的对象,Pair是Kotlin标准库的类,表示一对元素,可以用Pair的内容来初始化两个变量:

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

这个功能成为结构,再来看下mapOf函数的声明:

fun <K,V> mapOf(vararg values: Pair<K,V>): Map<K,V>

集合的解构可以如下使用

for((index,element) in collection.withIndex()) {
    println("$index: $element")
}

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

处理一个字符串的分割

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

//也可以这样用
>>> println("12.345-6.A".split(".","-")
[12, 345, 6, A]

再看另一个例子的两种不同实现,分割文件路径"/Users/zhazhahui/kotlin/info.txt",第一种如下

//1.使用String的扩展函数来解析文件路径
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")
}
>>> parsePath("/Users/zhazhahui/kotlin/info.txt")
Dir: /Users/zhazhahui/kotlin, name: info, ext: txt

第二种使用正则表达式

//2.使用正则表达式切割
fun parsePath(path: String) {
    val regex = """(.+)/(.+)\.(.+)""".toRegex()
    val matchResult = regex.matchEntire(path)
    if (matchResult != null) {
        //解构,与Pair的解构一致
        val (directory, fileName, extension) = matchResult.destructured
        println("Dir: $directory, name: $fileName, ext: $extension")
    }
}

    这里正则表达式写在一个三重引号的字符串中。在这样的字符串中,不需要对任何字符进行转义,包括反斜线,所以可以用. 而不是\. 来表示点,正如写一个普通字符串的字面值。在这个正则表达式中:第一段(.+)表示目录,/表示最后一个斜线,第二段(.+) 表示文件名,. 表示最后一个点,第三段(.+) 表示扩展名。
    三重引号字符串的目的,不仅在于避免转义字符,而且使它可以包含任何字符,包括换行符。它提供了一种更简单的方法,从而可以简单的把包含换行符的文本嵌入到程序中:

val kotlinLogo = """|//
        .|//
        .|/ \
    """.trimMargin(".")
>>> print(kotlinLogo)
|//
|//
|/ \

多行字符串包含三重引号之间的所有字符,包括用于格式化代码的缩进。如果要更好的表示这样的字符串,可以去掉缩进(左边距)。为此,可以向字符串内容添加前缀,标记边距的结尾,然后调用trimMargin 来删除每行中的前缀和前面的空格。在这个例子中使用了. 来作为前缀。

5.局部函数和扩展

先看一个简单的保存用户

class User(val id: Long,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到数据库
}

这里才校验了两个属性,但看起来已经造成了冗余,看下Kotlin的局部函数如何处理这个

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到数据库
}

可以继续改进,把验证逻辑放到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 save(user: User) {
    user.validateBeforeSave()
    //保存user到数据库
}