Kotlin之心路历程
以小白眼光看kotlin
kotlin这个小丫鬟被谷歌扶正为大房两年,期间看过不少博文,很多人也已经把个人项目迁移到kotlin了,当然国外的开发者更给力,直接大部企业开发已经kotlin,也订阅了涛哥的极客时间(一直没时间看,果然看视频太费事,还是文章可以抽得时间挤一挤学),一直是不想学习啊,一个人的惰性就是这样,java又不是不能用,代码通俗易懂。说句心里话,java8的lambda表达式我都没学,ps:不过好像用java8 lambda表达示的在工作中也没怎么碰到。。
第一眼看到kotlin,大部分人都是,哇,我被它惊艳了(至少表面上都这么说的),然而本渣狗并没有感觉(难道就我一个菜狗?),反而觉得:啥?为了一个空安全学新的知识,if(x!=null)它不香哦。直到最近换了工作,遗留代码一半是kotlin,糟了,是心动的感觉。(不得不上手写了)
其实最开始上手依然痛苦无比,lambda表达式各种省略,函数是一等公民这句废话几乎每个博客都写了,但是我看着太抽象啊,我管你几等公民,你告诉我它区别在哪啊,有啥用啊。
无非下面一些问题
-
空安全并没有让我香到学习新知识
空安全确实没有香到非学新东西不可,不过,它的设计思路还是不错的。判空无非两种做法,a. 在编写代码里运行时避免空指针异常,也就是常用的if(x!=null);b. 直接静态代码检查,在编译期就避免了空指针,kotlin其实就是这种了,比如定义为String,那么不好意思,别的地方你传个null,编译器就给你整红,非要可空,那后果你自己负,用String?吧。这样的好处是省略了大把逻辑判断,让编译器帮我们把把关。
-
Lambda表达式省略一时爽,不会写的人看不懂
最简单的也是出现最多的,莫过于点击事件了
//kotlin view.setOnClickListener { //TODO } //java view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { } }); 复制代码
大家其实看不习惯的原因就在于,没有()带参数,那我{}里要使用()传递过来的参数怎么办,其实编译器现在已经很智能的告诉我们了
我们在{}中可以通过it就能引用这个传递过来的view参数。至于Lambda表达式为什么能这么省略,我们到后面再讲。 -
函数是一等公民到底是个啥 无数次出现的函数是一等公民,到底讲的是啥?其实就一句话,函数可以做参数传递给变量不就得了。。。熟悉前端语言的其实应该很熟。
-
代码中无故出现+-等运算符 运算符重载在很多lib中能看到,像经常用的协程库就有
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main) 复制代码
我们自己写的话,可以这么写
fun main() { println(Foo(1, 2) + Foo(3, 4)) } data class Foo(val x: Int, val y: Int) { operator fun plus(other: Foo): Foo = Foo(x + other.x, y + other.y) } 复制代码
运算符很多,可以参考https://www.jianshu.com/p/d445209091f0
-
炫技的中缀表达式 炫技的大哥们挺喜欢这么写
println("I" am "OK")
复制代码
或者一段中文style
println("你" 在 "干嘛")
复制代码
这都是啥鬼哟,看看源码其实就好懂了,这就是一个简单的语法糖
infix fun String.am(any: Any) {
}
infix fun String.在(any: Any) {
}
复制代码
其实就是infix来修饰的中缀表达式,扩展了String,增加am方法,就是这么一回事而已,扩展函数搞不明白,后面会讲到(这是我最喜欢kotlin的一点了)
- DSL浪用 举例?要我这么懒的人来举例,怕是抬举我了,在网上找一个,
showDialog {
title = "title"
message = "message"
rightClicks {
toast("clicked!")
}
}
复制代码
嗯嗯,这配置代码确实写得很少简洁了,但是对初学是一眼万年(懵),dsl的动态性和高扩展性,让kotlin有了更多的活力,但是DSL是要精而专,如果不到位的话,写起来也没轻松多少,当然,做公共组件,比如在这里使用dialog的话,当你熟悉了的话,调用会快得多。
其实DSL说穿了就是扩展函数搞起,就那回事,主要是你要想得全,封装得好
inline fun AppCompatActivity.showDialog(settings: CustomDialogFragment.() -> Unit) : CustomDialogFragment {
val dialog = CustomDialogFragment.newInstance()
dialog.apply(settings)
val ft = this.supportFragmentManager.beginTransaction()
val prev = this.supportFragmentManager.findFragmentByTag("dialog")
if (prev != null) {
ft.remove(prev)
}
ft.addToBackStack(null)
dialog.show(ft, "dialog")
return dialog
}
复制代码
上面这段代码扩展了Activity,所以我们可以直接调用showDialog函数,里面唯一传入的参数是个lambda表达式,所以可以把{}直接放后面,别的都省略(lambda表达式后面马上就会讲)。showDialog( )方法中只有唯一的参数settings,其类型是CustomDialogFragment.() -> Unit,即带有CustomDialogFragment参数类型的函数。
在showDialog( )方法内部,构造了CustomDialogFragment对象,并调用dialog.apply(settings)方法,其作用即在构造Dialog对象后,对Dialog进行设置。在实际调用showDialog()方法时,就可以持有该CustomDialogFragment对象,然后调用CustomDialogFragment提供的public接口配置Dialog。
说一说香在哪
如果有朋友听我喽嗖到这里的话,那么说明我们都一样,是时候上硬菜了。
扩展库——协程
别给我整有的没有,我最开始香起来还就是协程了(手动滑稽)。
其实协程并不是一个新概念,很多现在语言都是有滴,像python(人生苦短,我用python),go这些现代语言,都是有协程的,从计算机的发展史来说,有可能协程的概念比线程还出来得要早。不过无所谓了,我也不当大历史家,现在我们使用的协程一般是在线程上的,虽然协程也可以直接运行在进程上,和线程毛关系都没有,但是我们做Android的,天生就有一条主线程。
很多人使用Rxjava看重的是啥?就是rxjava切换线程,顺滑如丝,那么,你如果只使用了rxjava的这一点特性的话,协程就足够了,性能还能直接提升不少。
概念太多,无非就是协程实际上就是极大程度的复用线程,通过让线程满载运行,达到最大程度的利用CPU,进而提升应用性能。我们知道,线程阻塞时,其实这条线程是闲置的,通过协程,我们可以在同一线程中运行多个协程任务,当这个协程任务挂起时,执行另外的协程,这样,线程就拉满了。
废话太多了,一哈讲不清,虽然有考虑后面会单独拎出来讲,但是好歹要给口糖,说下简单使用。
因为是扩展库,所以需要手动引入
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0"
复制代码
GlobalScope.launch(Dispatchers.Default) {
println(Thread.currentThread().name)
withContext(Dispatchers.Main) {
println(Thread.currentThread().name)
}
}
//运行结果
I/System.out: DefaultDispatcher-worker-1
I/System.out: main
复制代码
这样就线程切换了,如果要在主线程等异步线程工作完后拿到它的返回值呢
GlobalScope.launch(Dispatchers.Main) {
val job = async(Dispatchers.Default) {
println(Thread.currentThread().name)
"a"
}
println(job.await())
}
//控制台打印
I/System.out: DefaultDispatcher-worker-1
I/System.out: a
复制代码
ps:不推荐使用GlobalScope,可以使用MainScope,至于原因以及使用方法,以前再讲
同样,不同协程前传递数据,也是可以使用传递数据的,提供了channel很容易建立一个生产者——消费者模型
runBlocking {
val channel = Channel<Int>(2)
GlobalScope.launch {
for (i in 1..5) {
channel.send(i)
}
channel.close()
}
for (a in channel) {
println(a)
}
println("done")
}
复制代码
使用runBlocking,是因为必须channel运行在一个挂起suspend函数或者协程里,Channel<Int>(2)
参数2是一次发送两,默认是一个一个发。
协程先就只讲这么多了,先看下别的香法吧
扩展函数
扩展函数香到什么地步,反正我是无法抗拒了。试问写java的童子们,曾几何时不都想扩展系统或者第三方类的方法时,只能含泪util,那么,有了扩展函数的特性后,妈妈再也不用担心我不能好好装B了。
inline fun String.lastChar(): Char = this[this.length - 1]
fun main(){
println("abc".lastChar())
}
//////
// 输出c
复制代码
就是因为扩展函数的天马行空,才有了kotlin的自由自在,这时候我想到的是安迪在肖申克的高墙上,看着大伙享受着自己赢来的啤酒,这是自由的味道~
动态代理
什么,一句话就搞掂了动态代理?没错,就辣么轻松惬意
interface Base{
fun abc()
}
class BaseImpl(val x:Int):Base{
override fun abc() {
print(x)
}
}
class Derived(b:Base):Base by b
fun main(){
val b = BaseImpl(110)
Derived(b).abc()
}
// 控制台打印输出
// 110
复制代码
其中class Derived(b:Base):Base by b
就帮我们实现了动态代理
如果有兴趣可以反编译一下看java代码,其实kotlin是在编译里,会把我们的代码翻译成静态代理的代码,因为没有使用反射,要比jdk动态代理的效率高得多,而且,这样的代码写起来不香么?
真泛型
看到真泛型时,脑袋瓜子是不是嗡嗡的,其实我们在java中使用泛型时,是没法拿到这个泛型的类型的,JVM的泛型都是通过类型擦除来实现的,也就是说泛型类实例的实参在编译时被擦除,运行时不会被保留。kotlin运行在jvm上,一样会遇到这个问题,所以kotlin想了一个好方法,并解决了这个问题。
fun main(){
testT<String>()
}
inline fun <reified T> testT(){
println(T::class.java)
}
////class java.lang.String
复制代码
可以看到,我们直接拿到了传递进来的泛型类型,像Gson这些库,我们就完全可以重新封装一下了。
Lambda表达式
lambda是一个函数表达式,是匿名函数,用来表示函数类型的实例的一种方式。
这里要先了解一下高阶函数
来了来了,在kotlin中,函数是一等公民。
高阶函数是将函数用作参数或返回值的函数。
而lambda经常用在此处
Lambda 的语法规则是这样的:
- lambda 表达式总是括在花括号中
- 完整语法形式的参数声明放在花括号内,多参数的话用逗号隔开
- 函数体放在
->
之后 - 如果需要返回值,那么函数体的最后一个表达式会被视为返回值
以如下几条规则能够让 Lambda 的表示更加简洁:
-
如果参数类型能被推导出来,那么可以省略的类型标注
-
如果 lambda 表达式的参数未使用,那么可以用下划线取代其名称
-
如果 Lambda 只有一个参数并且编译器能识别出类型,那么可以不用声明这个参数并忽略 ->。 该参数会隐式声明为 it 。
-
如果函数的最后一个参数接受函数,那么作为相应参数传入的 lambda 表达式可以放在圆括号之外
-
如果该 lambda 表达式是调用时唯一的参数,那么圆括号可以完全省略。
还是拿view的点击事件来举例,用kotlin的完整写法是:
view.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View?) { //TODO } }) 复制代码
根据java中只有单个非默认抽象接口,在kotlin可以使用函数来表示,这时,可以精简为:
view.setOnClickListener({ v: View -> //TODO }) 复制代码
而lambda中唯一参数可以省略
view.setOnClickListener({ //TODO }) 复制代码
lambda表达式为唯一参数时,()可以省略,这样就变成了
view.setOnClickListener{ //TODO } 复制代码
all啦
其实lambda表达示只是在高阶函数中使用时,省略比较多我们用不习惯而已,习惯觉得挺爽的,不用再多写一堆代码了
小结
总的来说,kotlin是越用越香的那种,最开始从java写kotlin还真的曲线陡峭,骂骂娘不就好了,世间万物,难逃真香定律。
下面是我的公众号,欢迎大家关注我