窥探Kotlin世界(进阶语法)
窥探Kotlin世界(基本语法)
一、扩展函数
语法结构:
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@printStringplintln(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? = nulloperator 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? = nullfun 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.Mainval 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)}
}
窥探Kotlin世界(进阶语法)相关推荐
- 学习Kotlin(二)基本语法
推荐阅读: 学习Kotlin(一)为什么使用Kotlin 学习Kotlin(二)基本语法 学习Kotlin(三)类和接口 学习Kotlin(四)对象与泛型 学习Kotlin(五)函数与Lambda表达 ...
- 【学习笔记】JS进阶语法一事件进阶
内容整理自<从0到1Javascript快速上手>下半部分-进阶语法篇 示例:event对象keyCode属性获取键盘上下左右键 <!DOCTYPE html> <htm ...
- 【学习笔记】JS进阶语法一事件基础
内容整理自<从0到1Javascript快速上手>下半部分-进阶语法篇 示例:键盘松开一瞬间触发的事件 <!DOCTYPE html> <html><hea ...
- avd android 5.1,Kotlin开发进阶
Kotlin开发进阶 编辑 锁定 讨论 上传视频 本词条缺少信息栏.概述图,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧! <Kotlin开发进阶>是清华大学出版社出版的图书,作者 ...
- LaTeX 进阶语法
文章目录 LaTeX进阶语法 一. 样式排版 1. 字体和字号 1.1 字体样式 1.2 字号 1.3 ctex宏包更改中文字体 1.4 文字装饰 2. 段落格式和间距 2.1 长度和长度变量 2.2 ...
- Kotlin第4篇 【Kotlin】进阶视频课程-关东升-专题视频课程
Kotlin第4篇 [Kotlin]进阶视频课程-376人已学习 课程介绍 本视频是智捷课堂推出的一套"Kotlin语言学习立体教程"的视频第四部分,主要内容包括: ...
- 攻防世界进阶upload
攻防世界进阶upload 注册登陆后,发现上传页面 试着上传一个文件3.php: 内容如下: <?php eval(@$_POST['a']); ?> 我们试着将它改个名字抓包,并且改为j ...
- Go语言入门——进阶语法篇(三)
文章目录 进阶语法 指针 基本指针 高级指针 指针总结 面向对象 概述 对象 类 结构体 定义与初始化 添加方法 方法的注意事项 类型别名与类型定义的区别 工厂函数 接口 接口声明 接口实现 空接口 ...
- Vue 进阶语法和生命周期
文章目录 Vue 进阶语法和生命周期 16.Vue:生命周期[了解] 17.Vue:computed计算属性 18.Vue:watch监控属性 Vue 进阶语法和生命周期 a. 每个 Vue 应用都是 ...
最新文章
- java对cookie的操作_java对cookie的操作
- android 如何完全卸载Android Studio
- C# Hashtable和Dictionary区别
- 用 Visual Studio 和 ASP.NET Core MVC 创建首个 Web API
- signature=fc89d4352b6699754c14ce282ec75426,Method for Assembly of Nucleic Acid Sequence Data
- 访问linux服务主机,如何把Linux配置为日志服务主机。
- 误报的java.sql.SQLException: Parameter number 21 is not an OUT parameter
- Python 被爆大 Bug,攻击者可远程代码执行漏洞!
- 从佛罗伦萨记账到区块链,应用才是区块链崛起的真正标志
- atitit.研发管理--标准化流程总结---java开发环境与项目部署环境的搭建工具包总结...
- ANSYS命令流——圆柱体网格划分
- Java实习日记(2-2)
- 使用 SetProcessWorkingSetSize 降低程序内存
- Mysql技术内幕InnoDB存储引擎——InnoDB存储引擎
- 东野圭吾梦幻花读后感_东野圭吾《梦幻花》读书笔记
- Epic Games创始人Tim Sweeney:头戴显示技术将颠覆电子产业
- UDP之广播搜索局域网内设备信息
- 软件项目的可行性分析包括哪些方面?影响决策的关键因素又是什么?
- JDBC简介(Statement接口)
- python实现五环