kotlin 基础语法-02-函数-高阶函数

kotlin 基础语法-02-函数-高阶函数

本篇文章 主要介绍kotlin 中的函数、嵌套函数、扩展函数、 lambda 语法 、 高阶函数、内联函数 体会函数式编程的能力。

1.函数

我们已经很熟悉函数的声明了、
基本函数的声明语法如下:

fun  funName(arg1:String="默认值"):返回值类型{
    /// 函数体 
}

其中参数后边的默认值是可以省略的、 如果返回值为空 ,在java中void 、 在kotlin 可以使用Unit来表示, 当然也是可以省略的、 默认返回值为空 。

还有一个特性 就是如果函数体中只有一个语句的话、 那么可以这样来表示

fun sum(num1: Int , num2: Int ) = print("$num1 + $num2= " +( num1+ num2))

这里不再赘述, 我们继续。

2.函数嵌套

个人表示不建议、也不喜欢这样的写法、 在java中类似于内部类。
来一个demo 看一下

fun  demo(words:String){
    
    val  str = "hello kotlin"
    
    fun sayHello(i :Int = 10 ){
        println(str )
        
        if(i > 0){
            sayHello(i-1)
        }
        
    }
    
    sayHello()
}

打印结果就是:

hello 10
hello 9
hello 8
hello 7
hello 6
hello 5
hello 4
hello 3
hello 2
hello 1
hello 0

个人觉得这种坑逼的写法, 由于比较隐蔽、 内部函数可以访问外部函数变量、 而且外部的其他函数是无法调用改函数, 所以主要用途在于,某些条件下触发递归的函数、或者说根本就不希望外部函数访问到的函数。
当然个人写法,我是不会这样写的, 更不推荐这么写, 搞得跟中介似的。 好了, 我们继续。

3. 扩展函数的静态解析

扩展函数主要用于第三方sdk 或者一些自己无法控制的类的、这时候如果想给这个类添加一些方法或者成员变量。 这时候就可以使用扩展函数 。
来个例子:

fun printFile(fileName:String){
    var file = File(fileName)
    println(file.readText()
}

这里用的是系统内置的扩展函数、我们看一下 FileReadWrite.kt

/**
 * Gets the entire content of this file as a String using UTF-8 or specified [charset].
 *
 * This method is not recommended on huge files. It has an internal limitation of 2 GB file size.
 *
 * @param charset character set to use.
 * @return the entire content of this file as a String.
 */
public fun File.readText(charset: Charset = Charsets.UTF_8): String = readBytes().toString(charset)

有时候不晓得是怎么回事、 看一下源码就立刻明白了、 是不是很清楚。

大家要注意、Kotlin的扩展函数、是静态的给扩展类添加扩展函数、 也就是说这样的扩展、不具备继承性.

那么Java中如何使用呢?
来个栗子,

public static void main(String args[]){
    
    File file =  new File("xxx.txt")
    String content = FileKt.readText(file,Charsets.UTF_8)
    sysout(content)
    
}

我们继续,

4.lambda 闭包


     Runnable thread = new Runnable(){

            public void run(){

                Utils.sayMsg("hello test");
            }
        };

      new Thread(thread).start();
        

java 8 的lambda语法

     new Thread(
                () -> {Utils.sayMsg("hello test");}
        ).start();
   

我们看一下kotlin 的lambda语法

    val  thread = Thread(Runnable {  println("hello world , kotlin !")})

    thread.start()

我们再来看几种比较灵活坑逼的语法结构:

默认格式:

fun main(args:Array<String>){
    
    val thread = Thread({-> Unit})
    thread.start()
}

如果lambda是没有参数的,可以省略箭头符号:
run 函数没有参数 , 如下

fun main(args:Array<String>){
    val thread = Thread({})
    thread.start()
}

如果lambda是函数的最后一个参数, 可以将大括号放在小括号的外面, 如下:

fun main(args:Array<String>){
    val thread = Thread(){}
    thread.start()
}

如果函数只有一个参数并且这个参数是lambda,则可以省略小括号。如下:

fun main(args:Array<String>){
    val thread = Thread{ } 
    thread.start()
}

lambda闭包声明:


val echo = {
    name:String, age : Int ->
    
    println("$name , $age")
}


fun main(args:Array<String>){
    
/// 两种调用方式, 均可以 
//    echo("samuel", 28)
//     echo.invoke("jack",27)

}

5. lambda 的参数上限

如果我们不做特殊处理、我们声明的lambda闭包会直接默认编译成匿名内部对象:

Function1<String,Unit> echo = (Function1) echo.INSTANCE;

当然参数个数的上限是22个,如果超出,就会抛异常。
找到kotlin/jvm/functions/ package目录下的Functions类, 找到最多参数的Function22

/** A function that takes 22 arguments. */
public interface Function22<in P1, in P2, in P3, in P4, in P5, in P6, in P7, in P8, in P9, in P10, in P11, in P12, in P13, in P14, in P15, in P16, in P17, in P18, in P19, in P20, in P21, in P22, out R> : Function<R> {
    /** Invokes the function with the specified arguments. */
    public operator fun invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
}

如果想要传入更多的参数的话,需要我们手动的添加接口。 当然声明的类不能声明和系统包名相同的目录下。由于java和kotlin是可以相互引用的,用java声明也是可以的。

6.高阶函数

高阶的意思就是函数的参数是函数的函数 , 是不是很绕。 来个例子:

fun main(args:Arrays<String>){
    
    ///这是第一种写法、
    onlyif(true,{

        println("hello , world! only if ")
    })
    //// 第二种写法、 当lambda 闭包 作为函数的最后一个参数传入的时候、是允许写在小括号之外的。  
    onlyif(true){
        println("hello ,kotlin , only if ")
    }
    
}

////第一个参数是boolean 决定block 函数是否被调用,
/// 第二个参数是一个方法块, 返回值是Unit , 这里如果函数作为方法的参数声明的话 是不能省略的, 不能像main函数一样省略Unit关键字
fun onlyif(debug:Boolean ,block:()->Unit){
    if(debug) block()
}

函数作为函数的方法传递给函数时的格式为

 function::run

如果直接用. 分割传递、 则传递的函数的执行结果。

    val  runable = Runnable{
        println("balabala ")
    }
    
    /// 参数为空  , 返回值为Unit 的函数对象
    val fuction : () -> Unit

    fuction = runable::run

    onlyif(true,fuction)
    

lambda 表达是会被编译成匿名内部类, 如果类中存在大量重复的lambda 表达式则会造成大量无用的匿名内部类。

inline关键字

这时候使用inline 关键字去修饰高阶函数 放在fun 之前, 这样在编译期,编译期就会拆解函数调用为语句调用。 进而减少创建不必要的对象。


inline fun onlyif(debug:Boolean ,block:()->Unit){
    if(debug) block()
}

下边是使用inline 关键字编译后生成的代码

public static final void onlyif(boolean debug, Function0<Unit> block){
/// Function0 后边这个数字 代表的是 后边的函数有多少个参数
    if(debug){
        block.invoke();
    }
}

我靠, 这不是静态函数吗、 明白inline关键字的作用了吧 。

过度使用inline 关键字 会增加编译器的负担、 增大代码问题排查难度。
通常我们只会把高阶函数修饰为inline。

7. 总结

多上手敲、多尝试、多思考、 有问题评论区我们共同讨论进步。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章