Kotlin语法梳理(一)
目录
函数
Kotlin中的函数很灵活,它可以独立于类或接口之外存在,即顶层函数,也就是全局函数;也可以存在于别的函数中,即局部函数;还可以存在于类或接口之中,即成员函数。
fun 函数名(参数列表):返回值类型{
函数体
return 返回值
}
fun add(n1:Int,n2:Int) :Int{
val sum = n1 + n2
return sum
}
有的函数没有返回值,此时可以将函数的返回值类型声明为Unit,相当于Java的void;或者可以省略不写。
Nothing类型
Kotlin中提供一种特殊的数据类型Nothing,Nothing只用于函数返回类型声明,不能用于变量声明。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
}
Java中Lambda表达式捕获局部变量时,局部变量只能是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函数
网友评论