反编译扒皮kotlin系列(一)

10,133 阅读5分钟

写作本文的目的

kotlin虽然不是java的语法糖,但在作用上确实就是个语法糖,学起来虽然轻松,但是有些概念不经常使用容易埋坑, 多看看kotlin反编译以后的代码 可以加深对 语法糖的 理解。也可以避免用java的写法来写kotlin。熟悉了以后可以更轻松自如的写kotlin味道的kotlin代码。

const val 与 val

object MyObject {
    const val t1 = 1
    val t2 = 2
}

来看看反编译以后的代码

public final class MyObject {
   public static final int t1 = 1;
   private static final int t2 = 2;
   public static final MyObject INSTANCE;

   public final int getT2() {
      return t2;
   }

   private MyObject() {
   }

   static {
      MyObject var0 = new MyObject();
      INSTANCE = var0;
      t2 = 2;
   }
}

从代码中可以看到 访问t1 的时候 是直接通过 field来访问 访问t2 的时候 就是用的 方法了。

所以总结下区别:

const val 是public的 val 是private的,访问效率上 val 要低一些,const val 要高一些。 因为访问val是通过方法,要有一次方法调用。而const val 则是直接访问field 效率更高

== 与 ===

实话说 这个设计我觉得是一个非常失败的设计,都快和js一样了。但既然kotlin这样设计了 我们还是看看为什么这样设计

fun main() {
    var user1 = User3("wuyue", "t3")
    var user2 = user1.copy()
    println(user1 == user2)
    println(user1 === user2)

}
data class User3(var username: String, var password: String?)

他的执行结果:

在java中显然是没有=== 这个操作符的。但是kotlin中。 上面的代码 我直接解释成

== 在koltin中代表 对象中的field 是否相等。 === 代表引用是否相等。

来反编译看看

看下copy函数做了啥

看到这应该能明白kotlin的 copy函数 其实就是重新new了一个对象 当然是浅拷贝。

==走的 就是下面这个函数

显然这个eqauls 就是user3的equals了

看到这你应该就能明白kotlin中的 == 与 === 的区别是哪来的了。 同时也可以知道 data class 这种写法

会自动根据我们设定的field来帮我们生成equals方法。

kotlin中的解构

package com.wuyue

data class Response(var code:Int,var message:String)

fun execute():Response{
    val code=200
    var message="ok"
    return Response(code,message)
}

fun main()
{
    val(code,message)= execute()
    println(code)
    println(message)
}

这个代码相信很多人都看得懂, 看main函数里 的第一行代码 这样的写法其实和es6中的解构其实是差不多的 也就是说 我们可以在一个函数调用中 返回多个值 ,这样的写法比我们不停的get出来返回值 要高效的多。 但是注意了这里和go语言中的函数多返回值是不一样的。

我们来看看kotlin中 是如何实现解构的

首先可以看一下:

data class 的写法 生成的bean代码 中 是多了这2个函数的。

然后看看调用时的写法

不用解释了吧。。。所以看到这 你应该可以理解这句话: kotlin虽然不是java的语法糖,但在作用上确实是无限接近于语法糖的作用的

kotlin中神奇的扩展函数

fun main() {

    var a = "hello world"
    println(a.doubleToString())
}

fun String.doubleToString(): String {
    return this.toString() + "__" + this.toString()
}

初学kotlin的都觉得 扩展函数很神奇,比如上面的代码。 扩展了string的函数 增加一个doubleToString的函数, 函数的作用我就不写了,大家一看就懂。

现在来反编译看看 这到底是咋实现的

嗯 越来越像语法糖了。

inline 函数

还是上面的例子

inline fun String.doubleToString2(): String {
    return this.toString() + "__" + this.toString()
}


fun main() {

    var a = "hello world"
    println(a.doubleToString())
    println(a.doubleToString2())

}

fun String.doubleToString(): String {
    return this.toString() + "__" + this.toString()
}

你看我新增了一个内联函数,这东西干啥的?传说能提高性能?反编译看一看

一眼就看出来,加上inline关键字的地方 在实际函数调用时 其实就是直接调用了inline函数体里面的语句

而没有直接调用函数本身,相对而言可以节省一次函数调用。但是也就仅此而已了。 个人认为少一次函数调用出栈入栈 其实并提高不了多少效率。

反而的这样还会带来代码膨胀。假设你inline函数里面语句非常多,那么你如果在很多地方都调用了这个inline函数 可想而知 你调用的地方就会多出来非常多的代码。 代码膨胀的速度是飞快的。

kotlin支持 函数作为参数的真相

这个又是一个类似于js的功能。我们来看看kotlin实际是怎么做的


class View {
    interface OnClickListener {
        fun onClick(view: View)
    }

    fun setOnClickListener(listener: (View) -> Unit) {
    }
}


fun main() {

    var view = View()
    view.setOnClickListener {
        onClick(view)
    }

}

fun onClick(view: View) {
    println("被点击")
}

很像js的闭包了,java中就不可以传递一个函数作为一个参数,但是kotlin可以, 那我们看看是怎么做的

首先反编译可以看出来 其实在定义的时候 还是interface在起作用的。

我们最终调用的时候 也可以看出来 还是遵循的java的规范 传递的是一个实际的对象。并不是函数。

这里大家有可以试试 在回调函数这边增加个inline关键字,看看会发生什么,这里就不演示了

init函数

fun main() {

    var p1 = Persont()

}

class Persont constructor() {

    init {
        println("111")
    }

    init {
        println("222")
    }

}

运行以后结果如下:

反编译看一看:

所以说这个地方的init函数 无非就是 当你在kotlin中使用了主构造器的时候 把你的init里面的代码 按照顺序放到构造函数里面,其他没啥。 为什么要这么做? 当然是因为主构造器的写法没有方法体,所以需要这么写,仅此而已。