美文网首页
Kotlin学习(三):函数运用

Kotlin学习(三):函数运用

作者: 往事一块六毛八 | 来源:发表于2020-03-12 15:36 被阅读0次
    Kotlin函数运用.png

    4.1 函数的基本作用

    4.1.1 与Java声明函数方式的区别

    那么Kotlin对函数的使用跟Java相比,有哪些区别呢?先从最常见的onCreate方法入手,看看二者都有哪些区别,下 面是Java编写的onCreate函数代码:

    //java 
    override 
        public void onCreate(Bundle savedInstanceState){
            ...
        }
    
    //Kotlin
       override fun onCreate(savedInstanceState: Bundle?) {
          .... 
      }
    
    

    两相对比,可以看到二者主要有以下几点区别:
    (1) Java使 用“@Override"表示该函数重载父类的方法,而Kotlin使 用小写的“override"在同一行表达重载操作。
    (2) Java使 用“public"表示该函数是公共方法,而Kotlin默认 函数就是公开的,所以省略了关键字“public"。
    (3)Java使用“void"表示该函数没有返回参数,而Kotlin不存在关键字“void",若无返回参数,则不用特别说明。
    (4) Kotlin新增了关键字“fun”, 表示这里是函数定义,其格式类似于Java的关键字“class”,而Java不存 在关键字“fun”。
    (5) Java声明输入参数的格式为“变量类型变量名称”,而Kotin声 明输入参数的格式为“变量名称:变量类型”。
    (6) Kotlin引入 了空安全机制,如果某个变量允许为空,就需要在变量类型后面加个问号“?”。

    4.1.2输入参数的格式

     //& 没有输入参数,也没有输出参数
            fun getDinnerEmpty() {
                tv_process.text = "只有空盘子哦"
                tv_result.text = ""
            }
    
            //只有输入参数
            fun getDinnerInput(egg: Int, leek: Double, water: String, shell: Float) {
                tv_process.text = "食材包括:两个鸡蛋,一把韭菜,几瓢水"
                tv_result.text = ""
            }
    
            //输入参数存在空值
            fun getDinnerCanNull(egg: Int, leek: Double, water: String?, shell: Float) {
                tv_process.text = if (water != null) "食材包括:两个鸡蛋,一把韭菜,几瓢水" else "没有水就没法做汤了"
                tv_result.text = ""
            }
    

    注意:函数的调用跟在Java中一样

    4.1.3 输出参数的格式

    Java代码中,函数的返回参数类型在函数名称前面指定,形如“publicintmain..)”,但在Kotlin中,返回参数类型却在右括号后面指定,形如“fun main.(..):Int。对于习惯了Java 的开发者而言,Kotlin的这 种写法着实别扭,为了方便记忆,我们姑且把函数当作一种特殊的变量,那么定义函数就跟定义变量是同一种写法。

    /**
            * 跟变量的声明格式一样:
            * var i:Int
            *
            * 函数的声明
            * fun main():Int
            */
    

    既然函数被当作-一种特殊的变量,同时每个变量都有变量类型,假如函数存在返回参数,那么自然把返回参数的类型作为该函数的变量类型;要是函数不存在返回参数,也就是Java中的返回void,那该怎么办?这里得澄清一-下,Java使用void表示不存在返回参数,然而Kotlin的返回参数是一定存在的,即使开发者不声明任何返回参数,Kotlin函数也会默认返回一个Unit类型的对象。

     //无任何返回参数-->关键字Unit
            //Unit类型表示没有返回参数,也可直接省略Unit声明
            fun getDinnerUnit(): Unit {
                tv_process.text = "只有空盘子哦"
                tv_result.text = ""
            }
    

    有返回值的函数:

      //有输出参数-->return关键字
            fun getDinnerOutput(): String {
                tv_process.text = "只有空盘子哦"
                var dinner: String = "巧妇难为无米之炊"
                return dinner
            }
    

    4.2 输入参数的变化

    4.2.1 默认参数

    允许在定义函数的时直接指定输入参数的默认值。如果调用函数时没有给出某参数的具体指,系统就自动对改参数赋予默认值。
    格式:在声明输入参数后面加上等号及其默认值

    //带默认参数的函数定义
    fun getFourBigDefault(
                general: String,
                first: String = "造纸术",
                second: String = "印刷术",
                third: String = "火药",
                fourth: String = "指南针"
            ): String {
                var answer: String = "$general:$first,$second,$third,$fourth"
                return answer
            }
    
    //调用
     btn_default_fun.setOnClickListener {
                btn_default_fun.text = getFourBigDefault("中国的伟大发明\n")
            }
    

    4.2.2 命名参数

    如果不满意参数的默认值,也可在调用函数时输入新的值,例如四大发明的默认值不包含它们的发明者,现在想增加显示造纸术的发明者蔡伦,则调用getFourBigDefault函数时,注意给第二个参数填写指定的描述文字,代码示例如下:

      //给第二个参数填写指定的描述文字
            btn_default_fun2.setOnClickListener {
                btn_default_fun.text = getFourBigDefault("中国的伟大发明\n", "蔡伦发明的造纸术")
            }
    

    有时想要变更的参数并非第一一个默认参数,比如第二个默认参数的“印刷术”,为了解决这个不合理的地方,Kotlin又引进了命名参数的概念,说的是调用函数时可以指定某个参数的名称及其数值,具体格式形如“参数名=参数值这样。就前述的给“印刷术”改名而言,具体到Kotlin编码上面,可参见以下的示例代码: .

     //有时候想要变更的并非第一个默认参数,而是指定参数,Kotlin引进了命名参数的概念
            //格式:“参数名=参数值”
    
            btn_default_fun3.setOnClickListener {
                btn_default_fun.text = getFourBigDefault("中国的伟大发明\n", second = "活字印刷术")
            }
    

    4.2.3 可变参数

    这种随时添加的概念对应于函数定义里面的可变参数,在Java体系中,可变参数采用“0bje... args' '的形式;在Kotlin体系中, 新增了关键字vararg,表示其后的参数个数是不确定的。以可变的字符串参数为例,Java的 写法为“Strig... args",而Kotlin的写法为“vararg args: String?"。函数内部在解析的时候,Kotlin会 把可变参数当作-一个数组,开发者需要循环取出每个参数值进行处理,对应的Kotlin演示代码如下所示:

    /**
             * 可变参数--参数的个数不确定--使用关键字vararg
             * Java中可变的字符串参数为例-->“String ...args”
             * Kotlin可变参数的写法为-->vararg args:String?
             * 函数内部解析的时候,Kotlin会把可变参数当做一个数组,开发者需要循环取出每个参数值进行处理
             */
    
            var isOdd: Boolean = false
    
            fun getFourBigVararg(
                general: String,
                first: String = "造纸术",
                second: String = "印刷术",
                third: String = "火药",
                fourth: String = "指南针",
                vararg otherArray: String?
            ): String {
                var answer: String = "$general:$first,$second,$third,$fourth"
                //循环取出可变参数包含的所有字段
                for (item in otherArray) {
                    answer = "$answer,$item"
                }
                return answer
            }
    
            btn_default_fun4.setOnClickListener {
                btn_default_fun4.text = if (isOdd) getFourBigVararg("古代的四大发明")
                else getFourBigVararg("古代的七大发明", "造纸术", "印刷术", "火药", "指南针", "丝绸", "瓷器", "茶叶")
                isOdd = !isOdd
            }
    

    可变参数是数组的情况下-->因为可变参数相当于数组,所以先遍历可变参数,在遍历某个数组中的所有元素

    如此一来,可变参数就成了可变的数组参数,同样声明数组参数时也要加上vararg前缀,告诉编译器后面的数组个数是变化的。对应的函数声明代码修改如下:

     fun getFourBigVarargArray(
                general: String,
                first: String = "造纸术",
                second: String = "印刷术",
                third: String = "火药",
                fourth: String = "指南针",
                vararg otherArray: Array<String>
            ): String {
                var answer: String = "$general:$first,$second,$third,$fourth"
                //先遍历每个数组
                for (array in otherArray) {
                    //在遍历某个数组中的所有元素
                    for (item in array)
                        answer = "$answer,$item"
                }
                return answer
            }
            btn_default_fun5.setOnClickListener {
                btn_default_fun5.text = if (isOdd) getFourBigVarargArray("古代的四大发明") else
                    getFourBigVarargArray(
                        "古代的N大发明", "造纸术", "印刷术", "火药", "指南针", arrayOf("丝绸", "瓷器", "茶叶")
                        , arrayOf("国画", "中医", "武术")
                    )
                isOdd = !isOdd
            }
    

    总结一下,Kotlin引入了默认参数的概念,并加以扩展允许通过命名参数指定修改某个参数值,而Java是 不存在默认参数概念的。另外,Kotlin对Java的可变 参数功能进行了增强,不但支持普通类型的可变参数,而且支持数组类型的可变参数。

    4.3 几种特殊的函数

    4.3.1泛型函数

    定义泛型函数的时候,的再函数名称前面添加"<T>",表示以T声明的参数(包括输入参数和输出参数),其参数类型必须在函数调用时指定。

    /**
            * 定义泛型函数的时候,的再函数名称前面添加"<T>",表示以T声明的参数(包括输入参数和输出参数),
            * 其参数类型必须在函数调用时指定。
            */
           //Kotlin允许定义全局函数,即函数可在单独的kt文件中定义,然后其他地方也能直接调用
           var count: Int = 0
    
           fun <T> appendString(tag: String, vararg otherInfo: T?): String {
               var str: String = "$tag:"
               //遍历可变参数中的泛型变量,将其转换为字符串再拼接到一起
               for (item in otherInfo) {
                   str = "$str${item.toString()}"
               }
               return str
           }
           btn_default_fun6.setOnClickListener {
               btn_default_fun6.text = when (count % 3) {
                   0 -> appendString<String>("古代四大发明", "造纸术", "印刷术", "火药", "指南针")
                   1 -> appendString<Int>("小于10的素数", 2, 3, 5, 7)
                   else -> appendString<Double>("烧钱的日子", 5.20, 6.18, 11.11, 12.12)
    
               }
               count++
           }
    

    4.3.2 内联函数

    注意到前面定义泛型函数appendString时,是把它作为一个全局函数,也就是在类外面定义,而不在类内部定义。因为类的成员函数依赖于类,只有泛型类(又称模板类)才能拥有成员泛型函数,而普通类是不允许定义泛型函数的,否则编译器会直接报错。不过有个例外情况,如果参数类型都是继承自某种类型,那么允许在定义函数时指定从这个基类泛化开,凡是继承自该基类的子类,都可以作为输入参数进行函数调用,反之则无法调用函数。

    举个例子,Int、 Float和Double 都继承自Number类,但是假如定义一个输入参数形式为setArrayNumber(array:Array<Number>)的函数,它并不接受Array <Int>或者Array<Double>的入参。如果要让该方法同时接收整型和双精度的数组入参,就得指定泛型变量T来自于基类Number,即将“<T>”改为“<reified T:Number>”,同时在fun前面添加关键字inline,表示该函数属于内联函数。

    
        /**
         * 内联函数 :规定泛型的继承自某个类型,可单独定义在类的外部
         */
        inline fun <reified T : Number> setArrayStr(array: Array<T>): String {
            var str: String = "数组元素依次排列:"
            for (item in array) {
                str = "$str ${item.toString()},"
            }
            return str
        }
    
    //调用
        var int_array: Array<Int> = arrayOf(1, 2, 3)
            var foult_array: Array<Float> = arrayOf(1.0f, 2.0f, 3.0f)
            var double_array: Array<Double> = arrayOf(11.11, 22.22, 33.33)
    
      //只有内联函数才可以被具体化
            btn_default_fun7.setOnClickListener {
                when (count % 3) {
                    0 -> btn_default_fun7.text = setArrayStr(int_array)
                    1 -> btn_default_fun7.text = setArrayStr(foult_array)
                    else -> btn_default_fun7.text = setArrayStr(double_array)
                }
                count++
    
            }
    

    4.3.3 简化函数

    在“4.1.3输出参数的格式”中提到了可将函数当作一种特殊的变量,既然变量通过等号赋值,那么函数也允许使用等号对输出参数赋值。具体地说,如果一个函数的表达式比较简单,一两行代码就可以搞定,那么Kotin允许使用等号代替大括号。

     //列如:求N!
            fun factorial(n: Int): Int {
                if (n <= 1) return n
                else return n * factorial(n - 1)
            }
    
            //简化
            fun factorialSimple(n: Int): Int = if (n <= 1) n else n * factorial(n - 1)
    

    4.3.4 尾递归函数

    4.3.3小节的阶乘函数只是一个普通的递归函数,Kotlin体系还存在一种特殊的递归函数,名叫尾递归函数,它指的是函数末尾的返回值重复调用了自身函数。此时要在fun前面加上关键字tailrec,告诉编译器这是一个尾递归函数,则编译器会相应进行优化,从而提高程序性能。

     /**
             * 指的时函数末尾的返回值重复调用了自身函数。此时要在fun前面加上关键字tailrec
             * 告诉编译器这是一个尾递归函数,则编译器会相应进行优化,从而提高性能
             */
            tailrec fun findFixPoint(x: Double = 1.0): Double = if (x == Math.cos(x)) x else findFixPoint(Math.cos(x))
    

    4.3.5 高阶函数

    由于函数可以当做特殊变量,如果把A函数作为B函数的输入参数,此时,因为B函数的输入参数内嵌了A函数,故而B函数被称作为高阶函数,对应的A函数则为高阶函数的函数参数,又称函数变量。

    为了解释地更加清楚些,接下来看一个例子。对于一个数组变量,若想求得该数组元素的最大值,则可以调用该数组的max方法。现在有一个字符串数组,类型为Array<String>,倘若调用该数组的max方法,返回的并非最长的字符串,而是按首字母排序在字母表最靠后的那个字符串。比如有个字符串数组为arrayOf("How" , "do", "you" ,"do", "I'm ", "Fine'"),调用max方法获得的字符串为“you”,而不是长度最长的那个字符串“I'm”。

    当然,也可以写一个单独的函数专门判断字符串长度,然而要是哪天需要其他比较大小的算法,难道又得再写一个全新的比较函数?显然这么做的代价不菲,所以Kotlin引入了高阶函数这个秘密武器,直接把这个比较算法作为参数传进来,由开发者在调用高阶函数时再指定具体的算法函数。就获取字符串数组内部的最大值而言,实现该功能框架的高阶函数代码如下所示:

            //4.3.5 高阶函数
            /**
             * 由于函数可以当做特殊变量,如果把A函数作为B函数的输入参数,
             * 此时,因为B函数的输入参数内嵌了A函数,故而B函数被称作为高阶函数
             */
            //maxCustom()是高阶函数,这里的greater函数就像是个变量
            //greater函数有两个输入参数,返回布尔类型的输出参数
            //如果第一个参数大于第二个参数,就认为greater返回true,否则返回false
            fun <T> maxCustom(array: Array<T>, greater: (T, T) -> Boolean): T? {
                var max: T? = null
                for (item in array) {
                    if (max == null || greater(item, max))
                        max = item
                }
                return max
            }
    
       //调用
            var string_array: Array<String> = arrayOf("Hao", "do", "you", "do", "I'm", "fine")
            btn_default_fun8.setOnClickListener {
                btn_default_fun8.text = when (count % 4) {
                    //string_array.max()返回的时you
                    0 -> "字符串数组的默认最大值为${string_array.max()}"
                    //string_array.max()对应的高阶函数是maxCustom同时也是泛型函数,所以要在函数名称后面加上<String>
                    1 -> "字符串数组按长度比较的最大值为${maxCustom<String>(string_array, { a, b -> a.length > b.length })}"
                    //因为系统可以根据string_array判断泛型函数采用了String类型,故而函数名称后面的<String>也可以省略掉
                    2 -> "字符串数组的默认最大值(使用高阶函数)为${maxCustom(string_array, { a, b -> a > b })}"
                    else -> "字符串数组按去掉空格在比较长度的最大值为${maxCustom(string_array, { a, b -> a.trim().length > b.trim().length })}"
                }
                count++
            }
    
    image.png

    输出结果:


    image.png
    image.png
    image.png
    image.png

    4.4增强系统函数

    4.4.1 扩展函数

    Kotlin推出了扩展函数的概念,扩展函数允许开发者给系统类补写新的方法,而无须另外编写额外的工具类。比如系统自带的数组Array提供了求最大值的max方法,也提供了进行排序的sort方法,可是并未提供交换数组元素的方法。于是我们打算给Array数组类增加新的交换方法,也就是添加一个扩展函数swap。与普通函数定义不同的是,要在swap函数名称前面加上前缀“Array<Int>.",表示该函数扩展自系统类Array<Int>。下 面是用于交换数组元素的swap函数定义代码: .

      /**
             * 概念:系统自带的类提供的方法无法满足日常开发需求,于是乎开发者往往编写了很多工具类,由于工具类繁多,难以管理。
             * Kotlin推出了扩展函数的概念,扩展函数允许开发者给系统类补写新的方法,而无需编写额外的工具类。
             */
            //例如:给数组Array新增交换的方法。
            fun <T> Array<T>.swap(pos1: Int, pos2: Int) {
                //this表示数组变量自身
                val temp = this[pos1]
                this[pos1] = this[pos2]
                this[pos2] = temp
    
            }
    
            val array: Array<Double> = arrayOf(1.0, 2.0, 3.0, 4.0, 5.0)
            btn_default_fun9.setOnClickListener {
                //下标为0和3的两个数组元素进行交换
                array.swap(0, 3)
                btn_default_fun9.text = setArrayStr<Double>(array)
            }
    
    image.png

    4.4.2扩展高阶函数

    “4.3.5高阶函数”小节中提到的maxCustom同时结合了高阶函数和泛型函数的写法,其实还可以给它加上扩展函数的功能。由于该函数的目的是求数组元素的最大值,因此不妨将该函数扩展到Array<T>中去,扩展后的高阶函数代码示例如下:

      /**
         * 扩展高阶函数
         * 扩展:扩展类的方法
         * 高阶:将函数作为变量传递
         */
        fun <T> Array<T>.maxCustomize(greater: (T, T) -> Boolean): T? {
            var max: T? = null
            for (item in this) {
                if (max == null || greater(item, max)) {
                    max = item
                }
            }
            return max
        }
    
           //扩展高阶函数
            //扩展函数+高阶函数
    
            btn_default_fun10.setOnClickListener {
                btn_default_fun10.text = when (count % 4) {
                    0 -> "字符串数组的默认最大值为${string_array.max()}"
                    1 -> "字符串数组按长度比较的最大值为${string_array.maxCustomize<String>({ a, b -> a.length > b.length })}"
                    2 -> "字符串数组的默认最大值(使用高阶函数)为${string_array.maxCustomize({ a, b -> a > b })}"
                    else -> "字符串数组按去掉空格在比较长度的最大值为${string_array.maxCustomize({ a, b -> a.trim().length > b.trim().length })}"
                }
                count++
            }
    
    

    4.4.3 日期时间函数--扩展日期函数

    现在利用Kotlin的扩展函数就无须书写专门的DateUtil工具类,直接写几个系统日期类Date的扩展函数即可实现日期时间格式转换的功能。改写后的Date类扩展函数举例如下:

     //方法名称前面的Date.表示该方法扩展自Date类
            //返回的日期时间格式形如2019-08-19 10:00:00
            fun Date.getNowDateTime(): String {
                val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                return sdf.format(this)
            }
            //
    
            //只返回日期字符串
            fun Date.getNowDate(): String {
                val sdf = SimpleDateFormat("yyyy-MM-dd")
                return sdf.format(this)
            }
    
            //只返回时间字符串
            fun Date.getNowTime(): String {
                val sdf = SimpleDateFormat("HH:mm:ss")
                return sdf.format(this)
            }
    
            //返回详细的时间字符串,精确到毫秒
            fun Date.getNowTimeDetail(): String {
                val sdf = SimpleDateFormat("HH:mm:ss.SSS")
                return sdf.format(this)
            }
            //返回开发者指定格式的日期时间字符串
    
            fun Date.getFormatTime(format: String = ""): String {
                var ft: String = format
                val sdf = if (!ft.isEmpty()) SimpleDateFormat(ft)
                else SimpleDateFormat("yyyyMMddHHmmss")
                return sdf.format(this)
            }
    
            btn_default_fun11.setOnClickListener {
                btn_default_fun11.text = when (count++ % 5) {
                    0 -> "当前日期时间为${Date().getNowDateTime()}"
                    1 -> "当前日期为${Date().getNowDate()}"
                    2 -> "当前时间为${Date().getNowTime()}"
                    3 -> "当前毫秒时间为${Date().getNowTimeDetail()}"
                    else -> "当前中文日期时间为${Date().getFormatTime("yyyy年MM月dd日HH时mm分ss秒")}"
                }
            }
    
    

    4.4单利对象

    虽然扩展函数已经实现日期信息的获取,但是它的调用方式稍显烦琐,况且这些函数必须从某个已存在的类扩展而来,倘若没有可依赖的具体类,也就无法书写扩展函数。所以,Java编码常见的***Util工具类,某种程度上反而更灵活、适应面更广,那么Kotlin有没有专门]的工具类写法呢?

    鉴于此,Kotlin将 工具类的用法提炼了出来,既然这个东西仅仅是作为工
    具,那么一旦制定了规格就不会再改变了,不能构造也不能修改。故而Kotin使用对象关键字object加以修饰,并称之为“单例对象”,其实就相当于Java的工具类。

    下面是采取单例对象改写后的日期时间工具代码:

    import java.text.SimpleDateFormat
    import java.util.*
    
    object DateUtil {
        ////返回的日期时间格式形如2019-08-19 10:00:00
        val nowDateTime: String
            get() {
                val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                return sdf.format(Date())
            }
        //只返回日期字符串
        val nowDate: String
            get() {
                val sdf = SimpleDateFormat("yyyy-MM-dd")
                return sdf.format(Date())
            }
    
        //只返回时间字符串
        val nowTime: String
            get() {
                val sdf = SimpleDateFormat("HH:mm:ss")
                return sdf.format(Date())
            }
        //返回详细的时间字符串,精确到毫秒
        val nowTimeDetail: String
            get() {
                val sdf = SimpleDateFormat("HH:mm:ss.SSS")
                return sdf.format(Date())
            }
    
        //返回开发者指定格式的日期时间字符串
        fun getFormatTime(format: String = ""): String {
            var ft: String = format
            val sdf = if (!ft.isEmpty()) SimpleDateFormat(ft)
            else SimpleDateFormat("yyyyMMddHHmmss")
            return sdf.format(Date())
        }
    
    
    }
    

    外部若要访问单例对象的变量值,直接调用“对象名称.变量名称”即可

      /**
             * 相当于Java的工具类写法
             * & 使用到了object关键字
             * & 单利对象包含两部分:1、字符串的声明变量 2、获取变量值的get方法
             */
    
    
            btn_default_fun12.setOnClickListener {
                btn_default_fun12.text = when (count++ % 5) {
                    0 -> "当前日期时间为${DateUtil.nowDateTime}"
                    1 -> "当前日期为${DateUtil.nowDate}"
                    2 -> "当前时间为${DateUtil.nowTime}"
                    3 -> "当前毫秒时间为${DateUtil.nowTimeDetail}"
                    else -> "当前中文日期时间为${DateUtil.getFormatTime("yyyy年MM月dd日HH时mm分ss秒")}"
                }
            }
    
        }
    

    相关文章

      网友评论

          本文标题:Kotlin学习(三):函数运用

          本文链接:https://www.haomeiwen.com/subject/lidpjhtx.html