窥探Kotlin世界(进阶语法)

窥探Kotlin世界(基本语法)

一、扩展函数

语法结构:

fun ClassName.methodName(param1:Int,param2:Int):Int{
	return 0
}

说明:

相比于定义普通函数,定义扩展函数只需要在函数名的前面加上一个ClassName.的语法结构,就表示将该函数添加到指定类当中

例子1fun String.showToast(content:Content){
	Toast.makeText(contnet,this,Toast.LENGTH_SHORT).show()
}
"This is Toast".showToast(content)
例子2fun View.showSnackbar(text:String,dutation:Int = Snackbar.LENGTH_SHORT){
	Snackbar.make(this,text,duration).show()
}
view.showSnackbar("This is Snackbar")

二、运算符重载(operator)

语法糖表达式和实际调用对应的函数对照表

语法糖表达式实际调用函数
a + ba.plus(b)
a - ba.minus(b)
a * ba.times(b)
a / ba.div(b)
a % ba.rem(b)
a++a.inc()
a–a.dec()
+aa.unaryPlus()
-aa.unaryMinus()
!aa.not()
a == ba.equals(b)
a >= ba.compareTo(b)
a…ba.rangeTo(b)
a[b]a.get(b)
a[b] =ca.set(b,c)
a in bb.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)
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值