Kotlin生成.class放入java编译器
Kotlin 开发效率提高50%
Kotlin 完美兼容java
Kotlin 几乎解决了空指针问题
《第一行代码第三版》Kotlin
(十五)高阶函数
接收Lambda参数的函数就可以被称为具有函数式编程风格的API,而如果你想要定义自己的函数式API,就要借助高阶函数来实现了。
定义:如果一个函数接收另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就称为高阶函数。
Kotlin添加了函数类型的概念,使得能够传入一个函数,还能够返回一个函数。如果将函数类型声明添加到函数内部,那么这就是一个高阶函数。
函数类型的声明规则
(String,Int)-> Unit
// ->左边的部分就是用来声明该函数接收什么参数,如果不接受就写空括号
// ->右边的部分就是用来声明该函数的返回值是什么类型,如果没有返回值就用Unit
fun myFunExample(func: (String, Int) -> Unit) {
func("hello", 123)
}
高阶函数的作用
允许让函数类型的参数来决定函数的执行逻辑。即使是同一个高阶函数,只要传入不同的函数类型参数,那么它执行逻辑和最终的返回结果就可能完全不同。
fun num1Andnum2(num1: Int, num2: Int, func: (Int, Int) -> Int): Int {
//在函数里面,没有进行任何具体的运算,而是将num1和num2参数传给了第三个函数类型参数,并获取它的返回值
//最终把得到的返回值进行返回
val result = func(num1, num2)
return result
}
调用高阶函数
一、函数引用的方式(::函数名)
fun num1Andnum2(num1: Int, num2: Int, func: (Int, Int) -> Int): Int {
//在函数里面,没有进行任何具体的运算,
//而是将num1和num2参数传给了第三个函数类型参数,并获取它的返回值
val result = func(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
val result1 = num1Andnum2(num1, num2, ::plus)
val result2 = num1Andnum2(num1, num2, ::minus)
}
二、Lambda表达式方式:
fun StringBuilder.my_build(my_func: StringBuilder.() -> Unit): StringBuilder {
//给StringBuilder类定义了一个my_build拓展函数
//这个拓展函数接收一个函数类型参数,并且返回值类型也是StringBuilder
//StringBuilder.的语法结构是(ClassName.),它表示这个函数类型定义在哪个类当中
//好处是当我们调用build函数时传入的lambda表达式将会自动拥有StringBuilder的上下文
my_func()
return this
}
高阶函数的实现原理
被编译成java字节码以后,底层会将其转换为匿名类的实现方式
//这种方式会造成额外的内存和性能开销
Java代码:
public static int num1Andnum2(int num1, int num2, Function operation){
int result = (int)operation.invoke(num1,num2);
return result;
}
public static void main(){
int num1 = 100;
int num2 = 80;
int result = num1Andnum2(num1,num2,new Function(){
@Override
public Integer invoke(Integer n1, Integer n2){
return n1+n2;
}
})
}
内联函数
Kotlin提供了内联函数的功能,它可以将使用Lambda表达式带来的运行时开销完全消除;
只需要在定义高阶函数时加上inline关键字
inline fun StringBuilder.my_build(my_func: StringBuilder.() -> Unit): StringBuilder {
my_func()
return this
}
noinline与crossinline
问题:一个高阶函数如果接收了两个或者更多函数类型的参数,我们加上inline,那么就会自动将所有引用的lambda都进行内联。
>如果我们只想内联其中一个的话,就在不想内联的参数前面加上noinline
inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {
//这样就只会对block1进行内联
}
为什么需要非内联?
1)内联的函数类型参数只允许传递给另外一个内联函数;
2)同时内联函数所引用的Lambda表达式中是可以使用return关键字来进行函数返回的,而非内联函数只能进行局部返回;
3)将高阶函数声明成内联函数是一种良好的编程习惯
>crossinline 能够保证在内联函数的lambda表达式中一定不会使用return 关键字,声明了crossinline以后,我们就不能在lambda表达式中使用return关键字进行返回了。
(十六)高阶函数的应用
高阶函数非常适用于简化各种API的调用,一些API的原有用法在使用高阶函数简化之后,不管是在易用性还是可读性方法,都可能会有很大的提升。
简化SharedPreferences的用法
//java的思维
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("name", "Tim")
editor.putInt("age", 28)
editor.putBoolean("married", false)
editor.apply()
高阶函数的简化:
先新建一个SharedPreferences.kt文件,里面写如下代码:
fun SharedPreferences.openSP(func: SharedPreferences.Editor.() -> Unit) {
//openSP函数是一个高阶函数
val editor = edit()
editor.func()
editor.apply()
}
然后在项目里面这样子进行调用
getSharedPreferences("data", Context.MODE_PRIVATE).openSP {
//我们可以直接在SharedPreferences对象上调用open函数,
// 然后在Lambda表达式中完成数据的添加操作
putString("name", "Tim")
putInt("age", 28)
putBoolean("married", false)
}
//在core-ktx库里面Google给了我们类似openSP的方法,叫做edit{ }
简化ContentValues的用法
//java的思维
val values = ContentValues()
values.put("name", "Game of T")
values.put("pages", 720)
values.put("price", 20.88)
db.insert("Book", null, values)
高阶函数的简化:
先建一个ContentValues,kt文件,写一个高阶函数继承ContentValues:
fun cvOf(vararg pairs: Pair<String, Any?>): ContentValues {
//vararg关键字就是java的可变参数列表,这样就允许传入0个,1个,2个Pair类型的
//Pair类型是一种键值对的数据结构,因此需要通过泛型来指定它的键值分别对应什么类型的数据
val cv = ContentValues()
for (p in pairs) {
val key = p.first
val value = p.second
when (value) {
is Int -> cv.put(key, value)
is Long -> cv.put(key, value)
is Short -> cv.put(key, value)
is Float -> cv.put(key, value)
is Double -> cv.put(key, value)
is Boolean -> cv.put(key, value)
is String -> cv.put(key, value)
is Byte -> cv.put(key, value)
is ByteArray -> cv.put(key, value)
null -> cv.putNull(key)
}
}
return cv
}
然后在项目里面这样子进行调用
val values = cvOf(
"name" to "Game of T",
"pages" to 720,
"price" to 20.88
)
db.insert("Book", null, values)
//在core-ktx库里面Google给了我们类似cvOf的方法,叫做contentValuesOf()方法
(十七)泛型
和java中相同的部分
泛型允许我们在不指定具体类型的情况下进行编程,这样编写出来的代码将会拥有更好的扩展性。
->定义泛型类:
class MyClass<T> {
//将MyClass的泛型指定为T,
//于是method方法可以接收一个T类型的参数,
//并且它的返回值也是T类型
fun method(param: T): T {
return param
}
}
//调用
val myclass = MyClass<Int>()
val res = myclass.method(123)
定义泛型方法:
class MyClass {
//可以在调用method方法的时候指定类型
fun <T> method(param: T): T {
return param
}
}
//调用
val myclass = MyClass()
val res = myclass.method<Int>(123)
->泛型上界:
可以通过指定上界的方式来对泛型的类型进行约束,默认情况下为可空类型,即Any?;设置为不空类型,就写Any;设置为数字类型就<T:Number>。
可以看下apply函数的源码:
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
和Java中不同的部分(第十章)
(十八)委托
委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。
类委托
核心思想是将一个类的具体实现委托给另一个类去完成。类委托的意义是,如果我们只是让大部分实现调用辅助对象中的方法,少部分的方法实现由自己来写,甚至加入一些自己独有的方法,那么该类就会称为一个全新的数据结构,这就是委托模式的意义所在。
->使用by关键字
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
//定义一个MySet类,实现Set接口;
//在构造函数里面接收了一个HashSet参数;
//HashSet就是一个辅助对象,在Set接口所有的方法实现中都没由进行自己的实现,而是默认调用了辅助对象中相应方法的实现;
//如果我们要对某个方法进行重新实现,只需要单独重写那一个方法就可以了;
fun helloWorld() {
print("hello world")
}
override fun isEmpty(): Boolean {
return false
}
}
委托属性
核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。
->使用by关键字
我们让p属性的具体实现委托给了myDelegateClass类去完成,当调用p属性的时候会自动调用myDelegateClass类的getValue方法,当给p属性赋值的时候会自动调用myDelegateClass类的setValue方法。
class MyClass {
var p by myDelegateClass()
}
class myDelegateClass {
var propVal: Any? = null
operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
return propVal
}
operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
propVal = value
}
}
//getValue()方法接收2个参数,第一个参数用来声明委托功能可以在什么类中使用,第二个参数KProperty<*>是一个属性操作类,可以用于获取各种属性相关的值。
//setValue()方法接收3个参数,前两个和getValue方法相同,最后一个参数表示具体要赋值给委托属性的值,这个参数的类型必须和getValue()方法返回值的类型保持一致。
(十九)infix函数
to不是Kotlin中的关键字,之所以我们能够使用A to B 这样的语法结构是因为Kotlin提供了一种高级语法糖特性:infix函数。这种语法更接近于使用英语的语法来编写程序。
例:我们用infix函数去提升一下String类的startsWith函数的语法可读性
infix fun String.beginsWith(prefix: String): Boolean {
//我们借助infix函数来提高beginsWith函数的可读性。
return startsWith(prefix)//内部直接调用startsWith函数
}
调用的时候就直接可以使用beginsWith
if ("Hello Kotlin" beginsWith "Hello") {
//todo
}
有两个比较严格的限制
第一个是infix函数必须接收且只能接收一个参数
第二个是infix函数必须是某个类的成员函数(注意:拓展函数就是在某个类里面的)
看一下to函数的源码
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)