仅为个人笔记。
目录
1、扩展函数
定义:扩展函数表示即使在不修改某个类的源码的情况下,仍然可以打 开这个类,向该类添加新的函数。
定义扩展函数的语法结构:
相比于定义一个普通的函数,定义扩展函数只需要在函数名的前面加上一个
ClassName.
的语
法结构,就表示将该函数添加到指定类当中了。
//我们将lettersCount()方法定义成了String类的扩展函数,那么函数中就自动拥有了String实例的上下文。
fun String.lettersCount(): Int {
var count = 0
for (char in this){
if (char.isLetter()) {
count++
}
}
return count
}
val count = "ABC123xyz!@#".lettersCount()
2、运算符重载
Java
中有许多语言内置的运算符关键字,如+ - * / % ++ --
。而
Kotlin
允许我们将所有的运算符甚至其他的关键字进行重载,从而拓展这些运算符和关键字的用法。
在指定函数的前面加上
operator关键字,就可以实现运算符重载的功能了。指定函数比如说加号运算符对应的是
plus() 函数,减号运算符对应的是minus()
函数。
例子:
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
operator fun plus(newValue: Int): Money {
val sum = value + newValue
return Money(sum)
}
}
val money1 = Money(5)
val money2 = Money(10)
val money3 = money1 + money2
val money4 = money3 + 20
println(money4.value)
if ("hello".contains("he")) {
}
//借助重载的语法糖表达式
if ("he" in "hello") {
}
3、高阶函数
高阶函数的定义
定义:如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。(Kotlin 又增加了一个函数类型的概念)
定义一个函数类型:
->
左边的部分就是用来声明该函数接收什么参数的,多个参数之间使用逗号隔开,如果不接收任何参数,写一对空括号就可以了。而->
右边的部分用于声明该函数的返回值是什么类型,如果没有返回值就使用Unit
,它大致相当于
Java
中的
void
。
现在将上述函数类型添加到某个函数的参数声明或者返回值声明上,那么这个函数就是一个高
阶函数了,如下所示:
例子:
//第三个参数是一个接收两个整型参数并且返回值也是整型的函数类型参数。
fun num1AndNum2(num1: Int, num2: Int, operation: (Int, Int) -> Int): Int {
val result = operation(num1, num2)
return result
}
fun plus(num1: Int, num2: Int): Int {
return num1 + num2
}
fun minus(num1: Int, num2: Int): Int {
return num1 - num2
}
//调用
fun main() {
val num1 = 100
val num2 = 80
/*第三个参数使用了::plus和::minus这种写法。
这是一种函数引用方式的写法,表示将plus()和minus()函数作为参数传递给num1AndNum2()函数。*/
val result1 = num1AndNum2(num1, num2, ::plus)
val result2 = num1AndNum2(num1, num2, ::minus)
println("result1 is $result1")
println("result2 is $result2")
}
结果:
上述例子代码如果使用Lambda 表达式的写法来实现的话,代码如下所示:
内联函数
我们一直使用的
Lambda
表达式在底层被转换成了匿名类的实现方式。这就表明,我们每调用一次Lambda
表达式,都会创建一个新的匿名类实例,当然也会造成额外的内存和性能开销。为了解决这个问题,Kotlin
提供了内联函数的功能,它可以将使用
Lambda
表达式带来的运行时开销完全消除。
内联函数的用法非常简单,只需要在定义高阶函数时加上
inline
关键字的声明即可:
Kotlin
编译器会将内联函数中的代码在编译的时候自动替换到调用它的地方,这样也就不存在运行时的开销了。
noinline
与
crossinline
一个高阶函数中如果接收了两个或者更多函数类型的参数,这时我们给函数加上了inline
关键字,那么
Kotlin
编译器会自动将所有引用的Lambda 表达式全部进行内联。但是,如果我们只想内联其中的一个Lambda
表达式该怎么办呢?这时就可以使用
noinline
关键字了,如下所示:
内联函数和非内联函数的区别:
内联的函数类型参数在编译的时候会被进行代码替换,因此它没有真正的参数属性。非内联的函数类型参数可以自由地传递给其他任何函数,因为它就是一个真实的参数,而内联的函数类型参数只允许传递给另外一个内联函数,这也是它最大的局限性。
内联函数所引用的
Lambda
表达式中是可以使用return
关键字来进行函数返回的,而非内联函数只能进行局部返回。
使用
内联函数可能出现的错误:
如果我们在高阶函数中创建了另外的
Lambda
或者匿名类的实现,并且在这些实现
中调用函数类型参数,此时再将高阶函数声明成内联函数,就一定会提示错误。借助crossinline关键字就可以很好地解决这个问题:
内联函数的
Lambda
表达式中允许使用
return
关键字,和高阶函数的匿名类实现中不允许使用return
关键字之间造成了冲突。而
crossinline
关键字就像一个契约,它用于保证在内联函数Lambda
表达式中一定不会使用
return
关键字,这样冲突就不存在了,问题也就巧妙地解决了。
4、高阶函数的应用
简化SharedPreferences的用法
简化为:
解释:
首先,我们通过扩展函数的方式向
SharedPreferences
类中添加了一个
open
函数,并且它还接收一个函数类型的参数,因此open函数自然就是一个高阶函数了。由于open函数内拥有SharedPreferences的上下文,因此这里可以直接调用edit()方法来获取SharedPreferences.Editor对象。另外open函数接收的是一个SharedPreferences.Editor的函数类型参数,因此这里需要调用editor.block()对函数类型参数进行调用,我们就可以在函数类型参数的具体实现中添加数据了。最后还要调用editor.apply()方法来提交数据,从而完成数据存储操作。
可以看到,我们可以直接在
SharedPreferences
对象上调用
open
函数,然后在
Lambda
表达 式中完成数据的添加操作。注意,现在Lambda
表达式拥有的是SharedPreferences.Editor的上下文环境,因此这里可以直接调用相应的
put
方法来添加数据。最后我们也不再需要调用apply()
方法来提交数据了,因为
open
函数会自动完成提交操作。
简化ContentValues的用法
还记得学过的
mapOf()
函数的用法吗?它允许我们使用
"Apple" to 1
这样的语法结构快速创建一个键值对。这里我先为你进行部分解密,在Kotlin
中使用
A to B
这样的语法结构会创建一个 Pair对象。
简化为:
解释:
cvOf()
方法接收了一个Pair
参数,也就是使用
A to B
语法结构创建出来的参数类型,但是我们在参数前面加上了一个vararg
关键字,这是什么意思呢?其实
vararg
对应的就是
Java
中的可变参数列表,我们允许向这个方法传入0
个、
1
个、
2
个甚至任意多个
Pair
类型的参数,这些参数都会被赋值到使用vararg
声明的这一个变量上面,然后使用
for-in
循环可以将传入的所有参数遍历出来。再来看声明的Pair
类型。由于
Pair
是一种键值对的数据结构,因此需要通过泛型来指定它的键和值分别对应什么类型的数据。值得庆幸的是,ContentValues
的所有键都是字符串类型的,这里可以直接将Pair
键的泛型指定成
String
。但
ContentValues的值却可以有多种类型(字符串型、整型、浮点型,甚至是null),所以我们需要将Pair值的泛型指定成Any?。这是因为Any是Kotlin 中所有类的共同基类,相当于Java 中的Object,而Any?则表示允许传入空值。