几个特性,快速上手Kotlin

作者: 咸鱼正翻身 | 来源:发表于2018-12-16 22:34 被阅读39次

    前言

    因为工作需要,所以最近大量的时间都用在了对Kotlin的提升上。所以最近的文章基本都是关于Kotlin的了内容。

    这不是一个死扣细节的文章,而是一个帮助小伙伴们快速由Java(Android)转向Kotlin的文章,因此更多的是一种语法与思想混在的模式。

    正文

    不多扯淡,点进来的小伙伴们肯定都是来学技术的,开搞。

    一、属性

    各位,千万不要因为标题的属性,就觉得没什么营养,以为我要说什么var、val。不不不,往下看,kotlin中的属性大有文章。

    1.1、可观察属性Observable

    这个语言特性,是非常非常用意思,且实用的。不信?往下看,来一个demo:

    class User {
        var name: String by Delegates.observable("<no name>") {
            prop, old, new ->
            println("$old -> $new")
        }
    }
    
    fun main() {
        val user = User()
        user.name = "first"
        user.name = "second"
    }
    

    效果好不好,咱们看“疗效”,运行结果如下:

    image

    是不是觉得有点意思?我们name这个属性的任何变化,都被观察了。我们可以在回调函数中,“胡搞瞎搞”~至于怎么搞?whatever。
    有小伙伴可能想说,既然我监听了属性的变化,我可以不以偷偷改些属性呢?当然可以,不过我们需要下面这个函数。

    1.2、vetoable

    vetoable比较有意思,可以理解为一个hook函数,它接受一个用于判断的表达式,满足这个表达式的才会被赋值,否则丢弃。很简单,上demo:

    // 过滤不满足条件的set
    var max: Int by Delegates.vetoable(0) { property, oldValue, newValue ->
        newValue > oldValue
    }
    
    println(max) // 0
    
    max = 10
    println(max) // 10
    
    max = 5
    println(max) // 10
    

    2.1、延迟属性 Lazy

    官方解释(罗里吧嗦,不看也罢)
    lazy() 是接受一个 lambda 并返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。

    说白了就是懒加载。被lazy标识的变量,在被调用的时候会触发我们实现的表达式,

    (下文会重点聊表达式,这里我们只需要之后,表达式的最后一行,代表着return)

    并拿到表达式的返回值。但表达式只会执行一次,后续的调用,直接回去表达式的返回值。也就是我们常说的懒加载。

    val lazyValue: String by lazy {
        println("computed!")
        "Hello"
    }
    
    fun main() {
        println(lazyValue)
        println(lazyValue)
    }
    
    image

    看结果我们就很清晰了吧。

    委托

    上边说的这些东西,在Kotlin之中都统一称之为委托/委托属性。委托是比较好用的一种语言特性,甚至可以很巧妙的帮我们解决一些复杂的设计模式上的问题。
    这其中的有趣,还望小伙伴们自己去探索呦~


    二、表达式

    对于我的学习来说,表达式的不理解,最开始对我阅读代码造成了很大的困惑。主要是少了“相濡以沫”的return,搞得自己有点懵。所以这里,让我们聊一聊表达式,也算填了上文挖的坑。

    1.1、if表达式

    if这个关键字,在Kotlin中代表一个表达式,它会默认有一个return,就是表达式中的最后一行。比如:

    var num = if(...某个判断){
        666
    }else{
        66666
    }
    

    这里num的值就是666。既然我们的if有自带return的功能,那么我们Java中常用的?:(三元运算符)是不是就没办法用了?的确要三元运算符(条件 ? 然后 : 否则)在Kotlin中换了写法(但并非不能用),因为普通的 if 就能胜任这个角色。

    // 作为表达式
    val max = if (a > b) a else b
    
    val max = if (a > b) {
        print("Choose a")
        a
    } else {
        print("Choose b")
        b
    }
    

    ?:在Kotlin中表示: 如果左侧的值为空,就取右侧的值。

    更多有趣的符号用法,可以参考官网:https://www.kotlincn.net/docs/reference/keyword-reference.html

    1.2、关于return

    既然我们从if中,了解了if中的隐式return,那这里可能会有一个疑问,能不能显示的写一个return呢?答案是:不行
    因为在Kotlin中,return的语义是这样的:从最直接包围它的函数或者匿名函数返回。

    表示式不属于函数,所以不行,同样Lambda表达式也不行。不这里有些特殊情况,所以我们好好聊一聊:

    要退出一个 Lambda 表达式,我们必须使用一个标签,并且在 Lambda 表达式内部禁止使用裸 return,因为 Lambda 表达式不能使包含它的函数返回:

    // 这里forEach是一个Lambda表达式,我们使用**标签**的形式,使其return
    listOf(1, 2, 3, 4, 5).forEach lit@{
        if (it == 3) return@lit // 局部返回到该 lambda 表达式的调用者,即 forEach 循环
        print(it)
    }
    

    这里肯定有小伙伴质疑:我可以在forEach里直接return啊!没错,的确是可以。因为forEach内联函数。内联函数是可以return的:

    关于内联函数后文会有篇幅展开它。

    官方介绍:内联是被允许的(这种返回(位于 lambda 表达式中,但退出包含它的函数)称为非局部返回。)

    inline fun inlined(block: () -> Unit) {
        println("hi!")
    }
    fun foo() {
        inlined {
            return // OK:该 lambda 表达式是内联的
        }
    }
    fun main() {
        foo()
    }
    

    比如,这种return是合法的,因为foreach是内联函数

    fun hasZeros(ints: List<Int>): Boolean {
        ints.forEach {
            if (it == 0) return true // 从 hasZeros 返回
        }
        return false
    }
    
    public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
        for (element in this) action(element)
    }
    

    嘚吧嘚说了这么,日常能用到么?说实话,没有diao用。不过,遇到了咱们知道该如何解释,这也算是一种收获吧。

    2.1、When 表达式

    when是我们常用的switch的增强plus版。其最简单的形式如下:

    when (x) {
        1 -> print("x == 1")
        2 -> print("x == 2")
        else -> { // 注意这个块
            print("x is neither 1 nor 2")
        }
    }
    

    我们也可以检测一个值在(in)或者不在(!in)一个区间或者集合中。即满足某些条件:

    when (x) {
        in 1..10 -> print("x is in the range")
        in validNumbers -> print("x is valid")
        !in 10..20 -> print("x is outside the range")
        else -> print("none of the above")
    }
    

    另一种用法是检测一个值是(is)或者不是(!is)一个特定类型的值。

    由于智能转换,我们可以访问该类型的方法与属性而无需任何额外的检测。

    fun hasPrefix(x: Any) = when(x) {
        is String -> x.startsWith("prefix")
        else -> false
    }
    

    因为,说when是switch的plus不为过吧。更多有趣的用法,欢迎各位小伙伴留言补充呦~


    三、杂乱的小细节

    1.1、Class

    val c = MyClass::class
    

    返回的是KClass,如果我们需要Class,则要这样MyClass::class.java

    1.2、this

    要访问来自外部作用域的this(一个类 或者扩展函数, 或者带标签的带有接收者的函数字面值)我们使用this@label,其中 @label 是一个代指 this 来源的标签:

    class A { // 隐式标签 @A
        inner class B { // 隐式标签 @B
            fun Int.foo() { // 隐式标签 @foo
                val a = this@A // A 的 this
                val b = this@B // B 的 this
    
                val c = this // foo() 的接收者,一个 Int
                val c1 = this@foo // foo() 的接收者,一个 Int
    
                val funLit = lambda@ fun String.() {
                    val d = this // funLit 的接收者
                }
    
    
                val funLit2 = { s: String ->
                    // foo() 的接收者,因为它包含的 lambda 表达式
                    // 没有任何接收者
                    val d1 = this
                }
            }
        }
    }
    

    1.3、匿名函数

    var list = arrayListOf(1, 2, 3, 4, 5, 6)
            // lambda表达式
            list.filter {
                it > 3
            }
            // 规规矩矩的匿名函数
            list.filter(fun(it): Boolean {
                return it > 3
            })
            // 自动类型推断后简写的匿名函数
            list.filter(fun(it) = it > 3)
      
    

    对于具有表达式函数体的匿名函数将自动推断返回类型,而具有代码块函数体的返回类型必须显式指定(或者已假定为 Unit)
    匿名函数参数总是在括号内传递。 允许将函数留在圆括号外的简写语法仅适用于 lambda 表达式。

    Lambda表达式与匿名函数之间的另一个区别是非局部返回的行为。一个不带标签的 return 语句总是在用 fun 关键字声明的函数中返回。这意味着 lambda 表达式中的 return 将从包含它的函数返回,而匿名函数中的 return 将从匿名函数自身返回。

    1.4、内联函数

    使用高阶函数(关于高阶函数,可以看我之前的一篇文章)会带来一些运行时的效率损失。因为每一个函数都是一个对象,并且会捕获一个闭包( 即那些在函数体内会访问到的变量)。 我们都清楚,内存分配(对于函数对象和类)和虚拟调用会引入运行时的开销。所以此时就需要内联函数,来消除这种开销。
    官方使用了lock这个函数来解释这个问题:

    fun <T> lock(lock: Lock, body: () -> T): T {
      lock.lock()
      try {
        return body()
      }
      finally {
        lock.unlock()
      }
    }
    

    正常我们会这样调用这个函数lock(l) { foo() }。如果不加修饰的话,因为高阶函数的存在,会造成大量的对象对创建出来,所以我们这里我们需要用内联的方式消除这种额外。

    // 使用内联,代码编译后将变成这样
    l.lock()
    try {
        foo()
    }
    finally {
        l.unlock()
    }
    

    可能有小伙伴在这里会有一些懵逼,一时没有理解内联做了什么。接下来用三个函数,解释一下这个问题:

    // 一个函数中,调用了另一个函数。虚拟机势必要为funtion1的调用,增加很多开销。
    fun funtion(){
        var a =1+1+1
        funtion1()
    }
    
    fun funtion1(){
        var aa =1+1+1
        var bb =1+1+1
        var cc =1+1+1
    }
    
    // 那么我们使用内联之后,就变成了这样:
    fun funtion(){
        var a =1+1+1
        var aa =1+1+1
        var bb =1+1+1
        var cc =1+1+1
    }
    

    我们可以看到,内联之后,我们funtion1函数中的实现,仿佛就在funtion函数中一样。因此就相应的降低了这部分的消耗。

    内联可能导致生成的代码增加;不过如果我们使用得当(即避免内联过大函数),性能上会有所提升,尤其是在循环中的“超多态(megamorphic)”调用处。

    尾声

    OK,到此差不多关于Kotlin的基于语法的内容差不多就要告一段落了。接下来的文章基本就会围绕着Android中的Kotlin进行开展。
    希望各位小伙伴们可以从中有所收获呦~

    我是一个应届生,最近和朋友们维护了一个公众号,内容是我们在从应届生过渡到开发这一路所踩过的坑,以及我们一步步学习的记录,如果感兴趣的朋友可以关注一下,一同加油~
    个人公众号:IT面试填坑小分队

    相关文章

      网友评论

        本文标题:几个特性,快速上手Kotlin

        本文链接:https://www.haomeiwen.com/subject/ecrfkqtx.html