一、扩展函数
语法结构:
fun ClassName.methodName(param1:Int,param2:Int):Int{
return 0
}
说明:
相比于定义普通函数,定义扩展函数只需要在函数名的前面加上一个ClassName.的语法结构,就表示将该函数添加到指定类当中
例子1:
fun String.showToast(content:Content){
Toast.makeText(contnet,this,Toast.LENGTH_SHORT).show()
}
"This is Toast".showToast(content)
例子2:
fun View.showSnackbar(text:String,dutation:Int = Snackbar.LENGTH_SHORT){
Snackbar.make(this,text,duration).show()
}
view.showSnackbar("This is Snackbar")
二、运算符重载(operator)
语法糖表达式和实际调用对应的函数对照表
语法糖表达式 | 实际调用函数 |
---|---|
a + b | a.plus(b) |
a - b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a++ | a.inc() |
a– | a.dec() |
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a == b | a.equals(b) |
a >= b | a.compareTo(b) |
a…b | a.rangeTo(b) |
a[b] | a.get(b) |
a[b] =c | a.set(b,c) |
a in b | b.contains(a) |
语法结构:
class Obj{
operator fun plus(obj:Obj):Obj{
//处理相加逻辑
}
}
val obj1 = Obj()
val obj2 = Obj()
val obj3 = obj1 + obj2
说明:
将obj1与obj2两个对象相加,其实Kotlin会在编译时把它转换为obj1.plus(obj2)的调用方式
三、高阶函数
定义
如果一个函数接受另一个函数作为参数,或者返回值的类型是另一个函数,那么该函数就是高阶函数。
函数类型语法如下:
(String,Int) -> Unit
说明:
箭头左边的是函数接受的参数列表,右边的是函数声明的返回值,没有返回值用Unit表示类似于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
}
//调用方式一
num1AndNum2(100,200,::plus)
//调用方式二(直接使用Lambda方式,不需要预先定义好与其函数类型参数相匹配的函数)
num1AndNum2(100,200){n1, n2 ->
n1 + n2
}
扩展应用(ClassName.):
fun StringBuilder.build(block: StringBuilder.()->Unit):StringBuilder{
block()
return this
}
//解释:ClassName. 表示这个函数类型是定义在哪个类中的。使得传入的Lambda表达式自动拥有ClassName的上下文
//构建类似apply标准函数的实现方式
val list = listof("Apple","Banana","Orange")
val result = StrintBuilder().build{
append("Start eating fruit\n")
for(fruit in list){
append("fruit").append("\n")
}
append("Ate all fruit \n")
}
内联函数(inline)
定义:
只需要在高阶函数前面加个关键字inline,代码在编译时自动替换到调用的地方。节省运行时的开销(因为Lambda表达式会在运行时new一个匿名实例对象)
inline fun num1AndNum2(num1:Int,num2:Int,operation:(Int,Int)->Int):Int{
val result = operation(num1,num2)
return result
}
noinline与crossinline
noinline使用:
如果用inline声明的高阶函数,那么高阶函数中的所有函数类型参数都是内联函数,如果想使某个函数类型参数不为内联函数则使用noinline关键字
inline fun inlineTest(block1:()->Unit,noinline block2:()->Unit){}
内联函数与非内联函数的区别:
1.内联函数在编译时进行代码替换,因此它没有真正的参数属性。而非内联的函数参数可以自由地传递给其它任何函数,因为它是一个真实的参数(使用泛型时非常有用)
2.内联函数所引用的Lambda表达式可以使用return关键字进行函数返回(因为是编译时代码替换,所以可以直接return到最外层调用),而非内联函数只能进行局部返回(返回自身的Lambda函数作用域)
fun pringString(str:String,block:(String)->Unit){
println("printString start")
block()
println("printString end")
}
fun main(){
println("main start")
val str = ""
printString(str){s->
println(lambda start)
//这里表示进行局部返回(因为是非内联函数,不能直接使用return)
if(s.isEmpty()) return@printString
plintln(s)
println("lambda end")
}
println("main end")
}
crossinline使用:
内联函数的Lambda表达式中允许使用return关键字,和高阶函数中的匿名类实现中不允许使用return关键字之间的冲突,而使用crossinline关键字规定它保证在内联函数的Lambda表达式中一定不会使用return关键字(因为匿名类中return无法返回到最外层调用中)
inline fun runRunnable(crossinline block:()->Unit){
val runnable = Runnable{
block()//这里的return无法返回到最外层调用中,因为是匿名类中
}
runnable.run()
}
高阶函数应用, 小例子:
fun SharedPreferences.open(block:SharedPreferences.Editor.()->Unit){
val editor = edit()
editor.block()
editor.apply()
}
getSharedPreferences("data",Context.MODE_PRIVATE).open{
putString("name","Jim")
putInt("age",10)
}
四、类委托和委托属性——by关键字
类委托
委托顾名思义就是转接给某人来实现,其作用就是在委托的同时自己可以先搞点事情,不搞事那就使用默认委托中的实现(代理设计模式的味道)
class MySet<T>(val helperSet:HashSet<T>):Set<T> by helperSet{
//默认实现,也可不写直接继承Set的使用
override fun contains(element:T) = helperSet.contains(element)
//在返回前打印下
override fun isEmpty(){
println("start isEmpty")
helperSet.isEmpty()
}
}
如上代码by helperSet其实就是MySet类的委托实现,Set中有的方法,MySet中都有,MySet中可以添加自己独特的方法
委托属性
定义:
将一个属性(字段)的具体实现委托给一个类去实现
class MyClass{
var p by Delegate()
}
class Delegate{
var propValue: Any? = null
//第一个参数就是代理需要应用到的哪个类中,第二个参数是属性操作类,用于获取各种属性相关的值
operator fun getValue(myClass:MyClass,prop: KProperty<*>): Any?{
return propValue
}
//第三个参数必须和getValue的返回值保存一致
operator fun setValue(myClass:MyClass,prop: KProperty<*>, value: Any?){
propValue = value
}
}
其中Delegate类必须实现getValue()与setValue()这两个方法且方法前需要使用operator关键字,如果p变量使用val定义那么可以不实现setValue()方法
应用(lazy函数的实现)——延迟加载的原理:
class Later<T>(val block:() -> T){
val value: Any? = null
operator fun getValue(any: Any?, prop:KProperty<*>): T{
if(value == null){
value = block()
}
return value as T
}
}
fun <T> later(block:()-> T) = Later(block)
val uriMatcher by later{
val matcher = UriMatcher(UriMatcher.NO_MATCH)
matcher.addURI(AUTHORITY, "BOOK", bookDir)
matcher
}
五、泛型
泛型实化 (使a is T或T::class.java这样的语法成为可能)
条件:
函数必须是内联函数且必须加上reified关键字来修饰
inline fun <reified T> startActivity(context:Context){
val intent = Intent(context,T::class.java)
context.startActivity(intent)
}
startActivity<TestActivity>(content)
说明:在java中泛型实化是不可实现的,因为在编译器编译时已把泛型给擦除了
泛型的协变与逆变
应用由来:
Person类似Student的父类,但List<Person>不是List<Student>的父类(出于类型转换安全机制考虑是不应许的,如下代码举例说明),如何能实现这功能且解决类型转换安全问题呢? 所以就引进了协变和逆变的语法糖了
约定:
一个泛型类或者泛型接口中的方法,它的参数列表是接受数据的地方称为in位,它的返回值是输出数据的地方称为out位
open class Person(val name:String,val age: Int)
class Student(name:String,age:Int):Person(name,age)
class Teacher(name:String.age:Int):Person(name,age)
//1.错误版本
class SimpleData<T>{
private var data:T? = null
fun set(t:T?){
data = T
}
fun get():T?{
return data
}
}
fun main(){
val student = Student("Jim",9)
val data = SimpleData<Student>()
data.set(student)
handleSimpleData(data)//实际上编译会报错
val studentData = data.get()
}
fun handleSimpleData(data: SimpleData<Person>){
val teacher = Teacher("Tom",13)
data.set(teacher)
}
//2.协变(out)
//正确版本 (使用out定义入参和val初始化定义入参变量或者使用private修饰),总之使外部不可改变初始变量,达到保证类型转换安全
class SimpleData<out T>(val data:T?){
fun get():T?{
return data
}
}
fun main(){
val student = Student("Jim",9)
val data = SimpleData<Student>(student)
handleSimpleData(data)
val studentData = data.get()
}
//只能获取
fun handleSimpleData(data: SimpleData<Person>){
data.get()
}
//3.逆变(in)--具体应用实例可以参见系统Compparable的比较两个对象接口
interface Transformer<in T>{
fun transform(t:T):String
}
fun main(){
val trans = object: Transformer<Person>{
override fun transform(t:Person): String {
return "$(t.name) $(t.age)"
}
}
handleTransformer(trans)
}
fun handleTransformer(trans:Transformer<Student>){
val student = Student("Tom",18)
val result = trans.transform(student)
}
说明:在协变时,可以使用@UnsafeVariance注解来修饰in变量打破语法规则(可以在List、Set的源码中找到这种用法),使编译不报错但这种机制慎用、少用,存在类型转换安全问题
六、协程
定义:
和线程类似可以简单理解成一种轻量级的线程,它可以仅在编程语言的层面就能实现不同协程之间的切换,而线程需要依靠操作系统的调度,所以协程的效率很高
使用:
1.首先使用协程需要添加依赖库,因为Kotlin并没有纳入标准的API中
dependencies {
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"
//android专有
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"
}
2.如何启用一个协程(或者说创建一个协程作用域),有如下4种方法
-
GlobalScope.launch 可以任意地方调用,每次创建都是顶层协程,生产上不太建议使用不好维护
fun main(){ GlobalScope.launch { printlin("codes run in cooutine scope") //delay是一个非阻塞式的挂起函数,只会挂起当前协程(该函数只能在协程作用域或其它挂起函数中使用) delay(1500) printlin("codes run in corotine scope finished") } //会阻塞当前线程,该线程下的所有协程都会被阻塞 Thread.sleep(1000) } //说明:如上代码只会打印第一个printlin语句,因为delay函数式非阻塞的,主线程才睡眠1秒,所有第二句printlin语句不会打印,如何解决?使用runBlocking阻塞当前线程
-
runBlocking 可以任意地方调用,会阻塞线程,建议在测试环境中使用
fun main(){ runBlocking { printlin("codes run in cooutine scope") delay(1500) printlin("codes run in corotine scope finished") } //会阻塞当前线程,该线程下的所有协程都会被阻塞 Thread.sleep(1000) } //说明:这样就可以保证两次的printlin语言都能输出,runBlocking会一直阻塞线程,容易产生一些性能的问题
-
launch 只能在协程作用域和挂起函数中调用
fun main(){ val start = System.currentTimeMllis() runBlocking { repeat(10000){ launch{ printlin("launch1 begin") } launch{ printlin("launch2 begin") } } } val end = System.currentTimeMills() printlin(end - start) } //说明:同时创建两个协程且重复创建10000次,从打印的耗时看就能知道协程的效率是极高的(不到1秒的时间),如果同时创建这么多线程,估计程序就崩溃了
问题:
如果launch内部实现很复杂的话,通常我们会定义一个函数来封装下,但这有存在一个问题,单独封装的函数就没有协程的作用域了,Kotlin提供了一个suspend关键字,使它定义的函数声明成为挂起函数
suspend fun printlnCoroutine(){ printlin("XXXX") delay(1000) }
-
coroutineScope 只能在协程作用域中调用而且会阻塞当前协程,但不影响其他协程也不影响任何线程
3.如何取消一个协程
不管是GlobalScope.launch函数还是launch函数都会返回一个Job对象,只需调用Job对象的cancle()方法就可以取消协程
val job = GlobalScope.launch{
//处理逻辑
}
job.cancle()
协程的返回值
1.async关键字,使用async修饰函数能返回一个Deferred对象,然后调用Deferred对象的await()方法,而且用async修饰的函数会创建一个新的子协程
fun main(){
runBlocking{
val result = async{
5+5
}.await()
println(result)
}
}
2.withContext()函数,其实它是async函数的简化版
fun main(){
runBlocking{
//相当于val result = async{ 5+5 }.await(),唯一不同的是需要强制指定一个线程参数,这个参数有3中可选值Dispatcher.Default、Dispatcher.IO、Dispatcher.Main
val result = withContext(Dispatcher.Default){
5+5
}
printlin(result)
}
}
说明:除了coroutineScope函数外,其它所有的函数都是可以指定一个线程参数,只不过withContext()函数是强制要求指定
3.suspendCoroutine函数,必须在协程作用域或挂起函数中使用
用处:简化回调流程
suspend fun request(address:String):String{
return suspendCoroutine{ continuation ->
HttpUtil.sendHttpResquest(address, object: HttpCallbackListener){
override fun onFinish(response:String){
continuation.resume(response)
}
override fun onError(e:Exception){
continuation.resumeWithException(e)
}
}
}
}
suspend fun getBaiduResponse(){
try{
val response = request("http://www.baidu.com")
//请求成功处理
}catch(e:Exception){
//异常处理
}
}
//说明:请求成功调用Continuation的resume()方法恢复挂起协程,请求失败调用resumeWithException恢复挂起协程,传入异常原因
七、SDL构建语法结构
SDL定义:领域特定语音(Domain Specific Language),通过它可以编写出一些看似脱离其原始语法结构的代码,从而构建出一种专有的语法结构。本质就是用该编程语言封装一些方法的而已。
实现目标:类似Android中build.gradle构建脚本文件中的依赖实现,如下
dependencise {
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
}
用Kotlin的SDL实现如下:
class Dependency {
val libraries = ArrayList<String>()
fun implementation(lib: String){
libraries.add(lib)
}
}
fun dependecies(block:Dependency.()-> Unit):List<String>{
val dependecy = Dependency()
dependecy.block()
return dependency.libraries
}
fun main() {
val libraries = dependecies{
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
}
for (lib in libraries){
printlin(lib)
}
}