Kotlin之函数

在Kotlin中创建集合

    //set支持数字
 val set = hashSetOf(1,7,53)
  println(set.javaClass) // class java.util.HashSet
//    创建集合
  val list = arrayListOf(1,7,53)
    println(list.javaClass) // 
    //获取集合最后个元素
    println(list.last()) // 53
    //获取集合里最大值
    println(list.max()) // 53
    //获取集合里第一个值
    println(list.first()) // 1
  //toSting
    println(list)// [1, 7, 53]
    //创建map
    val map = hashMapOf(1 to "one",7 to "seven",53 to "fifty-three")
    println(map.javaClass) // class java.util.HashMap
//直接setof
   val numbers = setOf(1,14,2)
    println(numbers.max())// 14

函数

//kotlin的toString的函数基本写法

fun  joinToString(
        collection: Collection,//集合
        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()
}


//调用
fun main(args: Array) {
    val list = arrayListOf(1, 7, 53)
    println(joinToString(list, ";", "(", ")"))// (1;7;53)
}

命名参数

函数关注的第一个首要问题是其可读性,上面函数的基本实现如果不查看源码的函数声明很难知道对应参数,虽然可以借助IDEA或者studio3.0可以实现,但是仍然很隐晦,kotlin可以改善并做的更优雅

//调用
fun main(args: Array) {
   
    val list = arrayListOf(1, 7, 53)
    println(joinToString(collection = list, separator = ";", prefix = "(", postfix = ")"))// (1;7;53)
}

当调用一个kotlin定义函数时,可以显示地注明一些参数的名称,如果指定了一个参数名称,为避免混淆,那它所有的参数都该被注明(注:显示注明参数时候可以不用在意参数的顺序,调用java函数时候,不能采用命令参数)

默认参数值

java的重载函数太多,kotlin在声明函数时候,可以指定参数的默认值,这样可以指定参数默认值,避免创建重载参数

//默认参数名

fun  joinToString(
        collection: Collection,//集合
        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()
}

//调用
fun main(args: Array) {

    val list = arrayListOf(1, 7, 53)
    //常规写法(必须按照参数顺序)
   println(joinToString(list,",","","")) //1,7,53
    //默认命名参数(省略参数)
    println(joinToString(list)) //1,7,53
    //改写默认命名(可以省略排在末尾的参数)
    println(joinToString(list,";")) //1;7;53
}

常规语法必须按照参数声明中定义的参数顺序来给定参数,可以省略的只有排在末尾的参数,如果使用命名参数,如上所说,可以省略中间参数,也可以不按照函数声明中的参数顺序,可以自定义顺序。
注:参数的默认值是被编码到被调用的函数中,而不是调用的地方。如果改变了参数的默认值,并重新编译函数,没有给参数重新赋值的调用者,将使用新的默认值。
如果使用java调用kotlin的命名参数和默认参数函数,请添加注解@JvmOverloads让编译器生成重载函数。

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

同级包目录下新建KotlinTest.kt文件,将JoinToString移到KotlinTest,在

package src.main

fun  joinToString(
        collection: Collection,//集合
        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()
}

在MainTest.kt(与KotlinTest.kt在同级包下)里直接调用

//调用
fun main(args: Array) {

    val list = arrayListOf(1, 7, 53)
    //常规写法(必须按照参数顺序)
   println(joinToString(list,",","","")) //1,7,53
    //默认命名参数(省略参数)
    println(joinToString(list)) //1,7,53
    //改写默认命名(可以省略排在末尾的参数)
    println(joinToString(list,";")) //1;7;53

}

不同包下的顶层函数调用需导包,新建Package strings,在strings包下建JoinKt.kt,将joinToString函数移到JoinKt

fun  joinToString(
        collection: Collection,//集合
        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()
}

依旧在MainTest里调用


import src.strings.joinToString //不同包目录下调用顶层函数需导包
//调用
fun main(args: Array) {

    val list = arrayListOf(1, 7, 53)
    //常规写法(必须按照参数顺序)
   println(joinToString(list,",","","")) //1,7,53
    //默认命名参数(省略参数)
    println(joinToString(list)) //1,7,53
    //改写默认命名(可以省略排在末尾的参数)
    println(joinToString(list,";")) //1;7;53

}

顶层函数编译成class文件后实质上就是java的静态函数。
注:如果要改变包含Kotlin顶层函数的生成的类的名称,需要为这个文件添加**
@field:JvmName("...")**,在包名之上,文件最顶层

顶层属性与顶层函数一样,不同的是属性声明,不细说

val count = 0;
//如果是public staic final修饰,kotlin用const修饰符替代
const val COUNT = 0;

扩展函数和属性(重点,重中之重)

扩展函数是kotlin一大特色,可以与现有代码平滑的集成。也可以基于java库(JDK,Android及其它第三方框架)构建kotlin。

//初始写法(this可以省略)
/**
 * String.lastChar(): Char 接收者类型,由扩展函数自己定义
 * this.get(this.length - 1) 接收者对象,是该类型的一个实例
 * */
fun String.lastChar(): Char = this.get(this.length - 1)
//idea智能转换
fun String.lastChar(): Char = this[this.length - 1]

import src.strings.lastChar

//调用
fun main(args: Array) {

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

}

上述代码中,String是接收者类型,"Kotlin"就是接收者对象。
从某种意义来说,你已经为Sting类添加了自己的方法。即使字符串不是代码一部分,也没有源代码,也是可以根据需要去写扩展方法。
只要是JVM类型语言,只要会被编译成Java类,你就可以为这个类添加自己的扩展方法
注:扩展函数并不允许打破它的封装性,和在类内部定义的方法不同,扩展函数不能访问私有的或者是受保护的成员。 换个说法即:扩展函数内部,可以在接收者上调用该类的任意方法,无论成员函数还是扩展函数,都可以调用。在调用一方,扩展函数与成员函数没有区别。

导入和扩展函数

定义的扩展函数不会自动的在整个项目范围内生效(使用需要导入,避免偶然的命命名冲突),导入扩展函数语法与导入类语法相同

//导入扩展函数
import src.strings.lastChar 

//也可以用*导入
import src.strings.*

//用关键字as可以修改导入的类或者函数名称
import src.strings.lastChar as last

//调用
fun main(args: Array) {

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

  //as修改函数名后
    println("Kotlin".last()) // n 
}

当在不同包中,有重名函数时候,一般函数和类可以选择用全名来指出这个类或者函数,对于扩展函数(扩展函数名需简短),就需要as来重命名解决命名冲突了。

java调用扩展函数

扩展函数实质上也是静态函数,它把调用对象作为了它的第一个参数。调用扩展函数不会去创建适配对象或者运行时候的额外消耗。java调用扩展函数即调用这个静态函数,然后把接收者对象作为第一个参数传进去(与顶层函数相同,包含Java类的名称是由这个函数声明的文件名决定)。

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

作为扩展函数的工具函数

joinToString函数的终极版本

/**
 * 为Collection声明一个扩展函数
 * */

fun  Collection.joinToString(
       separator: String = ",",//元素间的分隔符
        prefix: String = "",//第一个元素分割符
        postfix: String = ""): String {
    val result = StringBuilder(prefix)
    //this指向接收者对象T的集合
    for ((index, element) in this.withIndex()) {
        if (index > 0) result.append(separator) //不用在第一个元素前添加分隔符
        result.append(element)
    }
    result.append(postfix) //最后个元素分隔符
    return result.toString()
}

//调用
fun main(args: Array) {

    val list = arrayListOf(1,2,3)
    //显示命名调用
    println(list.joinToString(separator = ";",prefix = "(",postfix = ")")) //(1,2,3)
    //默认命名调用
    println(list.joinToString(" ")) //1 2 3
    
}

扩展函数是静态函数的一个语法糖,可以使用具体类型作为接收者类型,而不是一个类。如果你想要一个joinToString函数只能由字符串类型触发。

fun Collection.join(
        separator: String=",",
        prefix: String="",
        postfix: String=""
)=joinToString(separator,prefix,postfix)

//调用
fun main(args: Array) {

    val list = listOf("one","two","three")
    //显示命名调用
    println(list.join(" ")) //one two three


}

如果是其它类型的对象调用,抛出异常。

    /**
     *     Error:(13, 13) Kotlin: Type mismatch: inferred type is List
     *         but Collection was expected
     */

    println(listOf(1, 2, 3).join(" "))

不可重写的扩展函数

在kotlin中,重写成员函数是很平常的,但是扩展函数的静态性质也决定了扩展函数不能被子类重写。

/**
 * 可继承的View,声明一个click函数
 */
open class View{
    open fun click()= println("View clicked")
}

/**
 * 继承View
 */
class Button:View(){
    override fun click() = println("Button clicked")
}

fun main(args: Array) {

    val view: View = Button()
    println(view.click()) // Button clicked

}

但对于扩展函数来说,并非如此。扩展函数并不是类的一部分,它是声明在类之外的。虽然可以给基类和子类都能分别定义一个同名扩展函数,但是当此函数调用时,是由变量静态类型决定,而不是这个变量运行时类型决定。

/**
 * View增加扩展函数showoff()
 */
fun View.showOff()= println(" this is View")


/**
 * Button增加扩展函数showOff
 */
fun Button.showOff()= println(" this is Button")

//调用
fun main(args: Array) {

    val view:View = Button()
    view.showOff() // this is View

}

尽管view现在声明的是Button对象,但是类型仍然是View。再次强调,扩展函数实质上是java的静态函数,而在上述中也有提到,扩展函数会把接受者作为第一个参数。
所以,Kotlin的扩展函数不存在重写一说,Kotlin会把扩展当作静态函数对待。
需要注意的是,如果一个类的成员函数和扩展函数名相同,成员函数会被优先使用。所以在扩展API时候,如果遇到此情况,那么对应类定义的消费者将重新编译代码,这将改变它的意义并开始指向新的成员函数(这句话暂时没明白啥意思,记住加黑的要点就行)。

扩展属性

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

/**
 * 因为这里没有支持字段,没有默认getter()实现,所以必须定义getter函数,
 * 初始化也不行,因为没有地方储存初始值
 */
val String.lastChar:Char
    get() = get(length-1)

//调用
fun main(args: Array) {

    //可以像访问成员属性一样访问
  println("kotlin".lastChar) //n
}

在StringBuilder上定义相同属性可以设置为var,因为StringBuilder是可变的

var StringBuilder.lastChar: Char
get() = get(length-1)  //getter()
set(value) = this.setCharAt(length-1,value) //setter()

//调用
fun main(args: Array) {

    //可以像访问成员属性一样访问
    val sb = StringBuilder("kotlin?")
    sb.lastChar='!'
    println(sb) // kotlin!

}

注:当从java访问扩展属性的时候,应该显示的调用它的getter函数。

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

kotlin语言的相关特性:
可变参数的关键字vararg,可以用来声明一个函数将可能有任意数量的参数

一个中缀表示法,当你在调用一些只有一个参数的函数时候,使用它会让代码更简练

解构声明,用来把一个单独的组合值展开到多个变量中

可变参数:让函数支持任意数量的参数
//创建集合,可以传递任意个数参数
  val list = listOf(2, 3, 5, 7, 11)
    /**
     * 源码,参数声明是vararg
     */
public fun  listOf(vararg elements: T): kotlin.collections.List { /* compiled code */ }

与Java不同(java声明是在类型后...)。kotlin传递的参数已经包装在数组中时候,调用该函数的语法。java可以原样传递数组,kotlin要求你显示的解包数组,方便每个数组元素在函数中作为单独参数调用(展开运算符),使用时候,在对应参数前加"*"。

//调用
fun main(args: Array) {
//通过展开运算符,可单个调用中组合来自数组的值和某些固定值
    val list = listOf("args:", *args)
    println(list) //[args:]

}

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

    //to并非是内置结构,而是特殊函数调用,即中缀调用
    //中缀调用中,没有添加额外分隔符,函数名称是直接放在目标对象名称和参数之间的
    //1 to "one"等价于 1.to("one")
    val map = mapOf(1 to "one",2 to "two",3 to "three")

中缀调用可以与只有一个参数的函数一起使用,无论是普通函数还是扩展函数,都需要关键字infix修饰符标记。

/**
 * to函数
 * 返回Pair类型对象,用来表示一对元素(函数声明皆为泛型,此处省略泛型)
 * */
infix fun Any.to(other:Any)= Pair(this,other)

可以用Pair直接初始化两个变量

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

解构还可以使用map的key和value来初始化两个变量(参考joinToString函数)

to是扩展函数,可以创建任意一对元素

    1 to "one"
    "one" to 1
    val list = listOf(1,2,3)
    println(list to list.size) //([1, 2, 3], 3)
    println("$list,${list.size}")//[1, 2, 3],3

字符串和正则表达式

分割字符串

//kotlin的split是一个扩展函数
//split传入显示的正则表达式
//kotlin的toRegex()也可以将字符串转为正则表达式
println("12.345-6.A".split("\\.|-".toRegex())) //[12, 345, 6, A]

//split重载支持文本分隔符
   println("12.345-6.A".split(".","-")) //[12, 345, 6, A]

java的split是实现不了此效果的,它会返回一个空数组,这是因为java的split()是一个正则表达式,而(.)表示任何字符的正则表达式。

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

使用扩展函数解析完整路径名到对应组件
示例路径:"/Users/yole/kotlin-book/chapter.adoc"

/**
 * 使用String的扩展函数来解析文件路径
 */
fun parsePath(path:String){
    //substringBeforeLast表示给定分隔符之前(第一次或最后次),/之前为目录
    val directory = path.substringBeforeLast("/")
    //substringAfterLast表示给定分割符之后(第一次或最后次) ,/之后文件名称
    val fullName = path.substringAfterLast("/")

    //同上,.之前文件名
    val fileName=fullName.substringBeforeLast(".")
    //同上,.之后扩展名
    val extension = fullName.substringAfterLast(".")

    println("Dir:$directory,name:$fileName,ext:$extension")
}


//调用
fun main(args: Array) {
    //Dir:/Users/yole/kotlin-book,name:chapter,ext:adoc
    parsePath("/Users/yole/kotlin-book/chapter.adoc")
}

使用正则表达式实现(正则表达式不熟悉不推荐)

fun parsePath(path:String) {
    val regex="""(.+)/(.+)\.(.+)""".toRegex()
    val matchResult = regex.matchEntire(path)
    if(matchResult!=null){
        val (directory,fileName,extension)=matchResult.destructured
        println("Dir:$directory,name:$fileName,ext:$extension")
    }

//调用
fun main(args: Array) {
    //Dir:/Users/yole/kotlin-book,name:chapter,ext:adoc
    parsePath("/Users/yole/kotlin-book/chapter.adoc")

}

创建一个正则表达式,输入路径匹配格式,符合(不为null)就destructured属性赋值给相应变量(与pair初始化变量效果相同)。不懂正则,略。只知道**kotlin中"""三重引号不需要对任何字符串转义
**
多行的三重引号字符串不仅可以避免转义字符,而且可以包含任何字符,甚至换行符。

//调用
fun main(args: Array) {

    val kotlinLogo = """|//
        .|//
        .|/\"""
    //trimMargin()函数可以用来删除每行前缀和空格
    println(kotlinLogo.trimMargin("."))
    
}
   /**
     * |//
     * |\\
     * |/\
     */

多行字符串中使用字符模板

//调用
fun main(args: Array) {
  //字符串中使用美元符号(与字符串模板冲突),必须使用嵌入表达式
    val price = """${'$'} 100"""
    println(price) //$ 100
}

扩展函数强大在于,可以扩展现有API适应新语言,即Pimp My Library 模式。kotlin标准库由标注java类扩展函数而来,Anko库是AndroidAPI的扩展。

让代码简洁:局部函数和扩展

kotlin可以在函数中嵌套提取的函数,这样可以便捷的获得所需结构,也无需额外的语法开销。

class User(val id:Int,val name:String,val address:String) {
    //带重复代码的函数
    fun saveUser(user: User){
        //检查字段
        if(user.name.isEmpty()){
            println("user.name is null")
        }
        //重复检查不同字段
        if(user.address.isEmpty()){
            println("user.address is null")
        }
    }
}

//调用
fun main(args: Array) {
    val user: User = User(1, "", "")
//    user.name is null
//    user.address is null
    println(user.saveUser(user))
}

将重复代码放入到局部函数

class User(val id:Int,val name:String,val address:String) {
  
    fun saveUser(user: User){
    //  局部函数
       fun validate(user: User,
                    value:String,
                    fieldName:String){
           if(value.isEmpty()){
               println("$value is null")
           }
       }
        validate(user,user.name,"Name")
    }
}

//调用
fun main(args: Array) {
    val user: User = User(1, "", "")

    println(user.saveUser(user))//is null
}

这样可以不用重复验证,也可以像User添加其它字段验证。
同时,上层楼,不用把User传给验证函数

class User(val id: Int, val name: String, val address: String) {
    fun saveUser(user: User) {
        //验证函数取消传入User
        fun validate(
                value: String,
                fieldName: String) {
            if (value.isEmpty()) {
                //局部函数可以直接访问外部函数
                println("${user.id}")
            }
        }
        validate( user.name, "Name")
    }
}

//调用
fun main(args: Array) {
    val user: User = User(1, "", "")

    println(user.saveUser(user))// 1
}

更上层楼,验证逻辑放到User扩展函数里

//扩展函数
fun User.validateBeforeSave(){
    //局部函数
    fun validate(
            value: String,
            fieldName: String) {
        if (value.isEmpty()) {
            println("is null")
        }
    }
    validate( name, "Name")
}

class User(val id: Int, val name: String, val address: String){
    fun saveUser(user: User){
        //调用扩展函数
        user.validateBeforeSave()
    }
}

//调用
fun main(args: Array) {
    val user: User = User(1, "", "")

    println(user.saveUser(user))// is null
}

提取到扩展函数中相当有效。即使User属于当前代码库而不是类库一部分,同时扩展函数validateBeforeSave()与用到User的地方并没有关系,不应该放到User的方法里。我们应该遵循类的API只包含必须的方法。
扩展函数也能使用局部函数,但是涉及到局部深度嵌套问题,不建议多层嵌套。

你可能感兴趣的:(Kotlin之函数)