Kotlin之心路历程

2,009 阅读10分钟

Kotlin之心路历程

以小白眼光看kotlin

kotlin这个小丫鬟被谷歌扶正为大房两年,期间看过不少博文,很多人也已经把个人项目迁移到kotlin了,当然国外的开发者更给力,直接大部企业开发已经kotlin,也订阅了涛哥的极客时间(一直没时间看,果然看视频太费事,还是文章可以抽得时间挤一挤学),一直是不想学习啊,一个人的惰性就是这样,java又不是不能用,代码通俗易懂。说句心里话,java8的lambda表达式我都没学,ps:不过好像用java8 lambda表达示的在工作中也没怎么碰到。。

第一眼看到kotlin,大部分人都是,哇,我被它惊艳了(至少表面上都这么说的),然而本渣狗并没有感觉(难道就我一个菜狗?),反而觉得:啥?为了一个空安全学新的知识,if(x!=null)它不香哦。直到最近换了工作,遗留代码一半是kotlin,糟了,是心动的感觉。(不得不上手写了)

其实最开始上手依然痛苦无比,lambda表达式各种省略,函数是一等公民这句废话几乎每个博客都写了,但是我看着太抽象啊,我管你几等公民,你告诉我它区别在哪啊,有啥用啊。

无非下面一些问题

  1. 空安全并没有让我香到学习新知识

    空安全确实没有香到非学新东西不可,不过,它的设计思路还是不错的。判空无非两种做法,a. 在编写代码里运行时避免空指针异常,也就是常用的if(x!=null);b. 直接静态代码检查,在编译期就避免了空指针,kotlin其实就是这种了,比如定义为String,那么不好意思,别的地方你传个null,编译器就给你整红,非要可空,那后果你自己负,用String?吧。这样的好处是省略了大把逻辑判断,让编译器帮我们把把关。

  2. Lambda表达式省略一时爽,不会写的人看不懂

    最简单的也是出现最多的,莫过于点击事件了

    //kotlin
    view.setOnClickListener {
    	//TODO
    }
    
    //java
    view.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 
             }
         });
    

    大家其实看不习惯的原因就在于,没有()带参数,那我{}里要使用()传递过来的参数怎么办,其实编译器现在已经很智能的告诉我们了

    我们在{}中可以通过it就能引用这个传递过来的view参数。至于Lambda表达式为什么能这么省略,我们到后面再讲。

  3. 函数是一等公民到底是个啥 无数次出现的函数是一等公民,到底讲的是啥?其实就一句话,函数可以做参数传递给变量不就得了。。。熟悉前端语言的其实应该很熟。

  4. 代码中无故出现+-等运算符 运算符重载在很多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

  5. 炫技的中缀表达式 炫技的大哥们挺喜欢这么写

println("I" am "OK")

或者一段中文style

println("你""干嘛")

这都是啥鬼哟,看看源码其实就好懂了,这就是一个简单的语法糖

infix fun String.am(any: Any) {
}
infix fun String.在(any: Any) {
}

其实就是infix来修饰的中缀表达式,扩展了String,增加am方法,就是这么一回事而已,扩展函数搞不明白,后面会讲到(这是我最喜欢kotlin的一点了)

  1. 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还真的曲线陡峭,骂骂娘不就好了,世间万物,难逃真香定律。


我的CSDN

下面是我的公众号,欢迎大家关注我