窥探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世界(进阶语法)相关推荐

  1. 学习Kotlin(二)基本语法

    推荐阅读: 学习Kotlin(一)为什么使用Kotlin 学习Kotlin(二)基本语法 学习Kotlin(三)类和接口 学习Kotlin(四)对象与泛型 学习Kotlin(五)函数与Lambda表达 ...

  2. 【学习笔记】JS进阶语法一事件进阶

    内容整理自<从0到1Javascript快速上手>下半部分-进阶语法篇 示例:event对象keyCode属性获取键盘上下左右键 <!DOCTYPE html> <htm ...

  3. 【学习笔记】JS进阶语法一事件基础

    内容整理自<从0到1Javascript快速上手>下半部分-进阶语法篇  示例:键盘松开一瞬间触发的事件 <!DOCTYPE html> <html><hea ...

  4. avd android 5.1,Kotlin开发进阶

    Kotlin开发进阶 编辑 锁定 讨论 上传视频 本词条缺少信息栏.概述图,补充相关内容使词条更完整,还能快速升级,赶紧来编辑吧! <Kotlin开发进阶>是清华大学出版社出版的图书,作者 ...

  5. LaTeX 进阶语法

    文章目录 LaTeX进阶语法 一. 样式排版 1. 字体和字号 1.1 字体样式 1.2 字号 1.3 ctex宏包更改中文字体 1.4 文字装饰 2. 段落格式和间距 2.1 长度和长度变量 2.2 ...

  6. Kotlin第4篇 【Kotlin】进阶视频课程-关东升-专题视频课程

    Kotlin第4篇 [Kotlin]进阶视频课程-376人已学习 课程介绍         本视频是智捷课堂推出的一套"Kotlin语言学习立体教程"的视频第四部分,主要内容包括: ...

  7. 攻防世界进阶upload

    攻防世界进阶upload 注册登陆后,发现上传页面 试着上传一个文件3.php: 内容如下: <?php eval(@$_POST['a']); ?> 我们试着将它改个名字抓包,并且改为j ...

  8. Go语言入门——进阶语法篇(三)

    文章目录 进阶语法 指针 基本指针 高级指针 指针总结 面向对象 概述 对象 类 结构体 定义与初始化 添加方法 方法的注意事项 类型别名与类型定义的区别 工厂函数 接口 接口声明 接口实现 空接口 ...

  9. Vue 进阶语法和生命周期

    文章目录 Vue 进阶语法和生命周期 16.Vue:生命周期[了解] 17.Vue:computed计算属性 18.Vue:watch监控属性 Vue 进阶语法和生命周期 a. 每个 Vue 应用都是 ...

最新文章

  1. java对cookie的操作_java对cookie的操作
  2. android 如何完全卸载Android Studio
  3. C# Hashtable和Dictionary区别
  4. 用 Visual Studio 和 ASP.NET Core MVC 创建首个 Web API
  5. signature=fc89d4352b6699754c14ce282ec75426,Method for Assembly of Nucleic Acid Sequence Data
  6. 访问linux服务主机,如何把Linux配置为日志服务主机。
  7. 误报的java.sql.SQLException: Parameter number 21 is not an OUT parameter
  8. Python 被爆大 Bug,攻击者可远程代码执行漏洞!
  9. 从佛罗伦萨记账到区块链,应用才是区块链崛起的真正标志
  10. atitit.研发管理--标准化流程总结---java开发环境与项目部署环境的搭建工具包总结...
  11. ANSYS命令流——圆柱体网格划分
  12. Java实习日记(2-2)
  13. 使用 SetProcessWorkingSetSize 降低程序内存
  14. Mysql技术内幕InnoDB存储引擎——InnoDB存储引擎
  15. 东野圭吾梦幻花读后感_东野圭吾《梦幻花》读书笔记
  16. Epic Games创始人Tim Sweeney:头戴显示技术将颠覆电子产业
  17. UDP之广播搜索局域网内设备信息
  18. 软件项目的可行性分析包括哪些方面?影响决策的关键因素又是什么?
  19. JDBC简介(Statement接口)
  20. python实现五环

热门文章

  1. Java二维码工具类(使用zxing实现,可支持logo)
  2. linux命令行的软件推荐
  3. 当贝OS版本更新:当贝智慧盒子Z1 Pro新增边看边聊,一起在线吐槽神剧
  4. 7-1 古埃及探秘-金字塔 (10分)
  5. 一个瑞典游戏工作室决定离开索尼,之前和之后都发生了什么?
  6. 《暮光之城●破晓(下)》
  7. 二十四节气-立冬文案、海报。万物收藏,冬之伊始。
  8. 1060显卡用什么软件测试,科技 篇七:有了它,GTX1060竟变智商检测卡!
  9. 【JavaScript】回调地狱、Promise
  10. 软件测试与正确性论证,OO学期总结