Kotlin语法梳理(二)

作者: 慕涵盛华 | 来源:发表于2018-09-18 17:28 被阅读13次

    Kotlin语法梳理(一)

    目录

    函数

    Kotlin中的函数很灵活,它可以独立于类或接口之外存在,即顶层函数,也就是全局函数;也可以存在于别的函数中,即局部函数;还可以存在于类或接口之中,即成员函数

    fun 函数名(参数列表):返回值类型{
      函数体
      return 返回值
    }

    fun add(n1:Int,n2:Int) :Int{
        val sum = n1 + n2
        return sum
    }
    

    有的函数没有返回值,此时可以将函数的返回值类型声明为Unit,相当于Java的void;或者可以省略不写。

    Nothing类型

    Kotlin中提供一种特殊的数据类型NothingNothing只用于函数返回类型声明,不能用于变量声明。Nothing声明的函数永远不会正常的返回,只会抛出异常。

    fun read(): Nothing {
        throw  IOException()
    }
    

    Nothing意思在于,有些框架,例如Junit单元测试框架,在测试失败时会调用Nothing返回类型的函数,通过它抛出异常使当前测试用例失败。

    采用命名参数调用函数

    fun getArea(width:Int,height:Int):Int{
    return width * height
    }
    getArea(width = 3,height = 4)
        或
    getArea(height = 4,width = 3)  
    

    可见采用命名参数调用函数,调用者能够清晰地看出传递参数的含义,命名参数对于有多参数函数调用非常有用。另外,采用命名参数函数调用时,参数顺序可以与函数定义时参数顺序不同

    注意 在调用函数时,一旦其中一个参数采用了命名参数形式传递,那么其后的所有参数都必须采用命名参数形式传递,除非它是最后一个参数。

    参数默认值

    在声明函数的时候可以为参数设置一个默认值,当调用函数的时候可以忽略该参数。

    fun getArea(width:Int,height:Int = 4):Int{
        return width * height
    }
    getArea(3)// 等价于getArea(3,4)
    

    采用参数默认的函数作用,类似java中的方法重载,Kotlin中提倡使用默认值的方式,因为参数默认值只需要声明一个函数就可以,而重载则需要声明多个函数。

    可变参数

    可以通过在参数名前面加vararg关键字的方式来表示这是可变参数。

    fun sum(vararg numbers:Int) :Int{
        var sum = 0
        for(number in numbers){
            sum += number
        }
        return sum
    }
    
    sum(1,2)
    sum(1,2,3,4)
    sum(1,2,3,4,5,6,7)
    

    注意:可变参数不是最后一个参数时,后面的参数需要采用命名参数形式传递。

    展开运算符“ * ”

    如果已经有一个数组变量,能否传递给可变参数呢?这需要使用展开运算符*

    val ary = intArrayOf(1,2,3,4)
    sum(*ary)
    

    表达式函数体

    如果在函数体中表达式能够表示成单个表达式时,那么函数可以采用更加简单的表示方式。

    fun add(n1:Int,n2:Int) :Int{
        val sum = n1 + n2
        return sum
    }
    
    fun add(n1:Int,n2:Int) :Int = n1 + n2
    

    局部函数

    声明在一个函数中的函数就叫做局部函数

    fun calculate(n1: Int, n2: Int, opr: Char): Int {
    
        //局部函数
        fun add(a: Int, b: Int): Int = a + b
    
        fun sub(a: Int, b: Int): Int = a - b
    
        return if (opr == '+') add(n1, n2)
        else sub(n1, n2)
    }
    

    内部函数的作用于是在外函数体内,如果直接访问局部函数,会发生编译错误

    函数式编程

    函数式编程(functional programming)是一种编程典范,也就是面向函数的编程。在函数式编程中一切都是函数。

    函数式编程核心概念如下:
    • 函数是“一等公民”:是指函数与其他数据类型是一样的,处于平等的地位。函数可以作为其他函数的参数传入,也可以作为其他函数的返回值返回。
    • 使用表达式,不用语句:函数式编程关心的输入和输出,即:参数和返回值。在程序中使用表达式可以有返回值,而语句没有。例如:控制结构中的if和when结构都属于表达式。
    • 高阶函数:函数式编程支持高阶函数,所谓高阶函数就是一个函数可以作为另外一个函数的参数或返回值。
    • 无副作用:是指函数执行过程会返回一个结果,不会修改外部变量,这就是“纯函数”,同样的输入参数一定会有同样的输出结果。

    Kotlin语言支持函数式编程,提供了函数类型、高阶函数和Lambda表达式。

    函数类型

    Kotlin中每一个函数都有一个类型,称为“函数类型”。函数类型作为一种数据类型与其他数据类型在使用场景上没有区别。可以声明变量,也可以作为其他函数的参数或者其他函数的返回值使用。

    //该函数的函数类型为:(Int,Int) -> Int
    fun rectangleArea(width:Int,height:Int):Int{
    return width * height
    }
    
    //声明一个变量,类型为:(Int,Int) -> Int 赋值为rectangleArea
    val getArea :(Int,Int) -> Int = ::rectangleArea
    
    //调用函数
    val area = getArea(3,4)
    

    Kotlin 中 双冒号操作符 表示把一个方法当做一个参数,传递到另一个方法中或者变量进行使用,通俗的来讲就是引用一个方法。

    函数类型就是把函数参数列表中的参数类型保留下来,再加上箭头符号和返回类型,形式如下:

    **参数列表中的参数类型 -> 返回类型 **

    (Int,Double) -> Double
    

    每一个函数都有函数类型,即便是函数列表中没有参数,以及没有返回值的函数也有函数类型(()->Unit)。

    函数字面量

    函数类型可以声明变量,那么函数类型变量能够接收什么的数据呢?即函数字面量如何表示?,函数字面量可以有三种表示:

    • 函数引用:引用到一个已经定义好的,有名字的函数。它可以作为函数字面量。
    • 匿名函数:没有名字的函数,即匿名函数,它也可以作为函数字面量。
    • Lambda表达式:Lambda表达式是一种匿名函数,可以作为函数字面量。
    fun calculate(opr:Char):(Int,Int) -> Int{
    
        fun add(a:Int,b:Int):Int = a + b //加法函数
    
        fun sub(a:Int,b:Int):Int = a - b //减法函数
    
        val result :(Int,Int) -> Int =
                when(opr){
                    '+' -> ::add   //函数引用
                    '-' -> ::sub   //函数引用
                    '*' -> {
                        fun(a:Int,b:Int) :Int = a * b  //匿名函数
                    }
                    else ->{ a,b ->  //Lambda表达式
                        a / b
                    }
                }
        return result
    }
    

    高阶函数

    可以把函数作为另一个函数的返回值或者参数使用,那么这个函数就属于高阶函数
    上述calculate函数就是一个高阶函数,返回值为(Int,Int) -> Int

    函数作为参数使用
    //funcName参数是函数类型
    fun getAreaByFunc(funcName:(Int,Int) -> Int,width:Int,height:Int) :Int{
    return funcName(width,height)
    }
    //调用
    var result = getAreaByFunc(::rectangleArea,3,4)
    

    Lambda表达式

    Lambda表达式是一种匿名函数,可以作为表达式、函数参数和函数返回值使用,Lambda表达式的运算结果是一个函数。

    语法格式

    Kotlin中的Lambda表达式很灵活,其标准语法格式如下:

    { 参数列表 -> Lambda体 }

    Lambda表达式的参数列表与函数的参数列表形式类似,但是Lambda表达式参数列表前后没有小括号。箭头符号将参数列表与Lambda体分隔开,Lambda表达式不需要声明返回类型。Lambda表达式可以有返回值,如果没有return语句Lambda体的最后一个表达式就是Lambda表达式的返回值,如果有return语句返回值是return语句后面的表达式。

    注意:Lambda表达式与函数,匿名函数一样都有函数类型,但从Lambda表达式的定义中只能看到参数类型,看不到返回值类型,但是返回值类型可以通过上下文推导出来。

    Lambda表达式也是函数类型,那么就可以声明变量,也可以作为其他函数的参数或者返回值使用。

    //funcName参数是函数类型
    fun getAreaByFunc(funcName: (Int, Int) -> Int, width: Int, height: Int): Int {
        return funcName(width, height)
    }
    //Lambda表达式作为函数参数使用
    val result = getAreaByFunc({ w, h -> w * h }, 3, 4)
    

    Lambda表达式简化写法

    Kotlin提供了多种Lambda表达式简化写法,下面介绍其中几种。

    • 参数类型推导简化: 类型推导是Kotlin的强项,Kotlin编译器可以根据上下文环境推导出参数类型和返回值类型。
    以下代码是标准形式的Lambda表达式: 
    { a: Int, b: Int -> a + b } 
    Kotlin能推导出参数a和b是Int类型,当然返回值也是Int类型。简化形式如下:
    { a, b -> a + b }
    
    • 使用尾随Lambda表达式: Lambda表达式可以作为函数的参数传递,如果Lambda表达式很长,就会影响程序的可读性。如果一个函数的最后一个参数是Lambda表达式,那么这个Lambda表达式可以放在函数括号之后。示例代码如下:
    fun getAreaByFunc(width: Int, height: Int, funcName: (Int, Int) -> Int): Int {
        return funcName(width, height)
    }
    //尾随Lambda表达式
    val result = getAreaByFunc(3, 4) { w, h -> w * h }
    

    如果没有其他的参数,可以省略括号

    fun printFunc(funcName: (Int, Int) -> Int) {
        println("${funcName(1, 2)}")
    }
    //调用printFunc方法,只有一个参数,类型是函数类型,传递的是Lambda表达式,括号可以省略
    printFunc { w, h ->
        w * h
    }
    

    注意:尾随Lambda表达式容易被误认为是函数声明,但它不是,而是函数调用

    • 省略参数说明:如果Lambda表达式的参数只有一个,并且能够根据上下文环境推导出它的数据类型,那么这个参数声明可以省略,在Lambda体中使用隐士参数it替代Lambda表达式的参数,示例代码如下:
    fun printFunc(str:String,funcName: (String) -> String) {
        println(funcName(str))
    }
    printFunc("省略参数",{
        it.reversed()   //隐士参数 it
    })
    

    Lambda体it隐式变量是由Kotlin编译器生成的,它的使用有两个前提:一是Lambda表达式只有一个参数,二是根据上下文能够推导出参数类型。

    闭包

    闭包(closure)是一种特殊的函数,它可以访问函数体之外的变量,这个变量和函数一同存在,即使已经离开了它的原始作用域也不例外。这种特殊函数一般是局部函数、匿名函数或Lambda表达式。

    闭包可以访问函数体之外的变量,这个过程称为捕获变量

    //全局变量
    var value = 10
    fun main(args: Array<String>) {
        //局部变量
        var localValue = 20
        val result = {a:Int ->
            value++
            localValue++
            val c = a + value + localValue
            println(c)
        }
        result(30)//输出62
        println("localValue = $localValue")//输出21
        println("value = $value")//输出11
    }
    

    JavaLambda表达式捕获局部变量时,局部变量只能是final的。在Lambda体中只能读取局部变量,不能修改局部变量。而Kotlin中没有这个限制,可以读取和修改局部变量。

    闭包捕获变量后,这些变量被保存在一个特殊的容器中被存储起来。即便是声明这些变量的原始作用域已经不存在,闭包体中仍然可以访问这些变量。

    内联函数

    高阶函数中参数如果是函数类型,则可以接收Lambda表达式,而Lambda表达式在编译时被编译称为一个匿名类,每次调用函数时都会创建一个对象,如果这种被函数反复调用则创建很多对象,会带来运行时额外开销。为了解决次问题,在Kotlin中可以将这种函数声明为内联函数

    内联函数在编译时不会生成函数调用代码,而是用函数体中实际代码替换每次调用函数。

    自定义内联函数

    Kotlin标准库提供了很多常用的内联函数,开发人员可以自定义内联函数,但是如果函数参数不是函数类型,不能接收Lambda表达式,那么这种函数一般不声明为内联函数。声明内联函数需要使用关键字inline修饰

    //参数类型有函数类型,声明为内联函数
    inline fun printFunc(str:String,funcName: (String) -> String) {
        println(funcName(str))
    }
    

    let函数

    Kotlin中任何对象都可以一个let函数let函数后面尾随一个Lambda表达式,在对象非空时执行Lambda表达式中的代码,为空时则不执行。

    a?.let { 
        print(a) //a不为空时输出,为空时不执行
    }
    

    with和apply函数

    有时候需要对一个对象设置多个属性,或调用多个函数时,可以使用with或apply函数。与let函数类似Kotlin中所有对象都可以使用这两个函数。

    在自定义view中当我们初始化画笔时很多时候我们会写下边的代码

    var paint = Paint()
    paint.color = Color.BLACK
    paint.strokeWidth = 1.0f
    paint.textSize = 18.0f
    paint.isAntiAlias = true
    

    如果使用with,那么就可以写成这样

    var paint = Paint()
    with(paint) {
        color = Color.BLACK
        strokeWidth = 1.0f
        textSize = 18.0f
        isAntiAlias = true
    }
    

    apply函数with函数类似,apply函数是有返回值的,它的返回值就是当前对象。 如果不需要返回值可以使用with函数

    相关文章

      网友评论

        本文标题:Kotlin语法梳理(二)

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