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 <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()
}


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

命名参数

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

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

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

默认参数值

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

//默认参数名

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()
}

//调用
fun main(args: Array<String>) {

    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 <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()
}

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

//调用
fun main(args: Array<String>) {

    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 <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()
}

依旧在MainTest里调用


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

    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<String>) {

    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<String>) {

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

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

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

java调用扩展函数

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

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

作为扩展函数的工具函数

joinToString函数的终极版本

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

fun <T> Collection<T>.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<String>) {

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

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

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

//调用
fun main(args: Array<String>) {

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


}

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

    /**
     *     Error:(13, 13) Kotlin: Type mismatch: inferred type is List<Int>
     *         but Collection<String> 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<String>) {

    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<String>) {

    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<String>) {

    //可以像访问成员属性一样访问
  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<String>) {

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

}

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

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

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

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

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

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

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

//调用
fun main(args: Array<String>) {
//通过展开运算符,可单个调用中组合来自数组的值和某些固定值
    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<String>) {
    //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<String>) {
    //Dir:/Users/yole/kotlin-book,name:chapter,ext:adoc
    parsePath("/Users/yole/kotlin-book/chapter.adoc")

}

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

//调用
fun main(args: Array<String>) {

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

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

//调用
fun main(args: Array<String>) {
  //字符串中使用美元符号(与字符串模板冲突),必须使用嵌入表达式
    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<String>) {
    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<String>) {
    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<String>) {
    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<String>) {
    val user: User = User(1, "", "")

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270