kotlin-jvm编译过程(一)

作者: typ0520 | 来源:发表于2017-06-14 14:14 被阅读2739次

    最近花时间看了下kotlin,语法和swift很像相对于java简洁很多。在android项目里kotlin现阶段还是寄生在jvm平台的,所有的kotlin源代码都会被编译成class文件,因此学习下kotlin编译成class的过程还是很有意义的,既可以加深对kotlin语法的理解也有助于排错。这篇文章的讲解是按官方教程的顺序来的(官方教程的中文版),建议先看一遍官方教程,然后结合本文一边动手去编译一边思考,可以通过jd查看编译出来的class的内容,经过两遍学习对kotlin语法就会有一个比较清晰的认识了。

    下面这几篇文章是大家整理的一些kotlin学习资料

    好了,下面让我们开始kotlin编译过程的学习之路吧

    变量、常量定义

    变量的定义使用var,常量定义使用val

    //hello.kt
    
    var a1 = 1000
    val b2 = 10
    

    kotlinc =>

    
    public final class HelloKt
    {
      public static final int getA1()
      {
        return a1;
      }
      
      public static final void setA1(int arg)
      {
        a1 = arg;
      }
      
      private static int a1 = 1000;
      
      public static final int getConst1()
      {
        return const1;
      }
      
      private static final int const1 = 10;
    }
    
    

    当kotlin中一个变量或者常量是文件级别的时候,编译以后会生成对应的私有静态变量或者私有类常量,当从java中调用的时候需要调用对用的get、set方法

    注: hello.kt编译出来的class文件名是HelloKt.class,是把kt文件名字的所有单词的首字母变成大写并且在后面加上Kt

    //packages.kt
    
    package foo.bar
    
    fun baz() {}
    
    class Goo {}
    

    java中的package大家肯定特别熟悉,kotlin编译的时候会根据package的值(例子中的foo.bar)创建对应的文件系统目录,并把编译出来的class放进去

    ➜  work tree;kotlinc packages.kt;tree
    .
    └── packages.kt
    
    0 directories, 1 file
    .
    ├── META-INF
    │   └── main.kotlin_module
    ├── foo
    │   └── bar
    │       ├── Goo.class
    │       └── PackagesKt.class
    └── packages.kt
    
    3 directories, 4 files
    

    控制流

    在kotlin中控制流相关的操作符有if、for、while、when,前三个和java中差不多,when取代了java中的switch操作符

    when

    //control-flow.kt
    
    when (x) {
        1 -> print("x == 1")
        2 -> print("x == 2")
        3, 4 -> print("x == 0 or x == 1")
        5..10 -> print("x in [5,10]")
        "string" -> print("x == string")
        else -> {
            print("else")
        }
    }
    

    执行kotlinc control-flow.kt进行编译

    control-flow-err.png

    编译无法通过错误信息是error: expecting a top level declaration,是不是觉得很奇怪什么鬼东西!!

    让我们站在编译器的角度来看,control-flow.kt文件最终编译的产物是Control_flowKt.class,我们知道java中在顶层永远是class,而类中只能包含类变量、类常量、静态方法、实例变量、实例方法、注解,对了还有static代码块;当编译器编译上面那段代码时编译器就懵逼了,这他吗的让我放在Control_flowKt.class的那个位置!抛给你一个异常你想要存在于顶级区域我提供不了!!!

    注: 其实从实现上来说可以把这段code编译到Control_flowKt.class的static区域

    方法可以存在于class中,那么我们就把这段代码放在方法中

    //control-flow.kt
    
    fun hello(x: Any) {
        when (x) {
            1 -> print("x == 1")
            2 -> print("x == 2")
            3, 4 -> print("x == 0 or x == 1")
            5..10 -> print("x in [5,10]")
            "string" -> print("x == string")
            else -> {
                print("else")
            }
        }
    }
    

    这次编译成功了

    =>

    import java.io.PrintStream;
    import kotlin.Metadata;
    import kotlin.jvm.internal.Intrinsics;
    import kotlin.ranges.IntRange;
    import org.jetbrains.annotations.NotNull;
    
    public final class Control_flowKt
    {
      public static final void hello(@NotNull Object x)
      {
        Intrinsics.checkParameterIsNotNull(x, "x");
        Object localObject = x;
        String str;
        if (Intrinsics.areEqual(localObject, Integer.valueOf(1)))
        {
          str = "x == 1";
          System.out.print(str);
        }
        else if (Intrinsics.areEqual(localObject, Integer.valueOf(2)))
        {
          str = "x == 2";
          System.out.print(str);
        }
        else if ((Intrinsics.areEqual(localObject, Integer.valueOf(3))) || (Intrinsics.areEqual(localObject, Integer.valueOf(4))))
        {
          str = "x == 0 or x == 1";System.out.print(str);
        }
        else if (Intrinsics.areEqual(localObject, new IntRange(5, 10)))
        {
          str = "x in [5,10]";
          System.out.print(str);
        }
        else if (Intrinsics.areEqual(localObject, "string"))
        {
          str = "x == string";
          System.out.print(str);
        }
        else
        {
          str = "else";
          System.out.print(str);
        }
      }
    }
    

    很明显是使用if语句实现的

    • 看下hello方法的第一行Intrinsics.checkParameterIsNotNull(x, "x");
      按说检查是否为空把x传进去就行了,但是后面还跟了一个"x",我猜是当x的值为null抛异常
      时用来拼接错误信息的,因为参数名这些元数据有可能会丢失,比如一个依赖了kotlin写的library并且开启了混淆的android项目,运行 起来如果在这个地方报错,如果没有明确的错误信息不太好排错
    • 5..10这个范围类型被编译成了new IntRange(5, 10)

    if

    kotlin中的if语句是有返回值的,比如下面这段java代码

    int result = 0;
    
    if (a > b) {
        result = a
    } else {
        result = b
    }
    
    //result = a > b ? a : b
    

    在kotlin可以这样写

    val result = if (a > b) a else b
    

    我们看一个稍微复杂一点的编译过程

    var a = 10
    var b = 100
    
    var result = if (a > b) a else b
    val result2 = if (a > b) {
        print("Choose a")
        a
    } 
    else {
        print("Choose b")
        b
    }
    

    =>

    import java.io.PrintStream;
    import kotlin.Metadata;
    
    public final class IfKt
    {
      public static final int getA(){ return a; }
      
      public static final void setA(int _a){ a = _a; }
      
      private static int a = 10;
      
      public static final int getB(){ return b; }
      
      public static final void setB(int _b){ b = _b; }
      
      private static int b = 100;
      
      public static final int getResult() { return result; }
      
      public static final void setResult(int _result){result = _result;}
      
      private static int result = a > b ? a : b;
      
      public static final int getResult2(){ return result2; }
      
      static{
        String str = "Choose a";
        System.out.print(str);
        str = "Choose b";
        System.out.print(str);
      }
      
      private static final int result2 = a > b ? a : b;
    }
    
    

    这段if语句被编译成了三元表达式,比较有意思的是print("Choose a")和print("Choose b")被编译进了static代码块里

    先看一个简单的类定义

    //class1.kt
    
    class Invoice {
    }
    
    //如果一个类没有类体,可以省略花括号。
    class Empty
    

    执行下编译

    class1.png

    可以看出编译后生成了两个class文件(Empty.class、Invoice.class),按照上面那些例子的编译经验应该还要生成一个Class1Kt.class,这次显然是没有的,因为class1.kt中所有的内容都有对应的Class归属,我们来做个试验,添加一些变量定义、方法定义的code

    //class2.kt
    
    class Invoice {
    }
    
    var x = 100
    
    fun hello() {
    }
    

    调用kotlinc进行编译


    class2.png

    可以看出这次生成了Class2Kt.class,以容纳除类定义外的code

    我们来看下Invoice.class的内容

    import kotlin.Metadata;
    
    public final class Invoice {
    }
    

    kotlin中类定义如果不加任何描述符,编译后的java类会带上public final描述符

    构造函数

    //constructor1.kt
    
    class Person constructor(firstName: String) {
         init {
           print("Person initialized with value ${firstName}")
        }
    }
    
    /*
    上面的代码可以简写为
    class Person(firstName: String) {
         init {
            print("Person initialized with value ${firstName}")
        }
    }
    */
    

    kotlinc constructor1.kt =>

    //Person.class
    
    import java.io.PrintStream;
    import kotlin.Metadata;
    import org.jetbrains.annotations.NotNull;
    
    public final class Person
    {
      public Person(@NotNull String firstName)
      {
        String str = "Person initialized with value " + firstName;
        System.out.print(str);
      }
    }
    

    编译以后会在Person.class中生成对应的构造方法,并且把init{}代码块中的内容放进生成的构造函数里面,这个特性还是挺有用的,举个例子我们在android开发中自定义一个View,通常会覆盖多个构造方法,如果需要做初始化操作一般会这样写

    public class CustomView extends View {
        public CustomView(Context context) {
            super(context);
    
            init();
        }
    
        public CustomView(Context context, @Nullable AttributeSet attrs) {
            super(context, attrs);
    
            init();
        }
    
        public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
    
            init();
        }
    
        public void init() {
            //do something
        }
    }
    
    

    在所有的构造方法里都要调用一下init挺烦的,kotlin的init{}代码块能更简洁的完成这个操作

    class CustomView : View {
        init {
            //do something
        }
        
        constructor(ctx: Context) : super(ctx)
        
        constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
        
        constructor(ctx: Context, attrs: AttributeSet, defStyleAttr: Int) : super(ctx, attrs, defStyleAttr)
    }
    

    init{}代码块也不是万能的

    //constructor2.kt
    
    class Person {
        init() {
            print("Person initialized ${firstName}")
        }
    
        constructor(firstName: String) {
            print("constructor1: ${firstName}")    
        }
    
        constructor(firstName: String,secondName: String) {
            print("constructor2 ${firstName} ${secondName}")
        }
    }
    

    这段init{}代码块访问的firstName,虽然在所有构造方法参数列表中都存在,但编译是通不过的

    constructor2.png

    如果kotlin的编译器不检查init{}代码块中firstName,由于所有的构造方法都有firstName因此到java这边其实是合法的

    继承

    kotlin的继承是通过冒号:操作符来完成的,相当于java中的extends,所有被继承的类定义时需要加open操作符

    //BaseActivity.java
    public class BaseActivity {
    }
    
    //LoginActivity.java
    public class LoginActivity extends BaseActivity {
    }
    

    上面这段java代码如果用kotlin写的话,按照我们的思维惯性会这样写

    //extends.kt
    
    open class BaseActivity {
    }
    
    class LoginActivity : BaseActivity {
    
    }
    

    很不幸这段kotlin代码是无法通过编译的,错误信息里提示必须指定一个构造函数

    extends.png
    //extends2.kt
    
    open class BaseActivity {
    }
    
    class LoginActivity : BaseActivity() {
    
    }
    
    //extends3.kt
    
    open class BaseActivity {
    }
    
    class LoginActivity : BaseActivity() {
        constructor() : super()
    }
    

    这两段代码是等同的,最终编译出来的class在结构上是一样的
    kotlinc =>

    //BaseActivity.class
    public class BaseActivity {}
    
    //LoginActivity.class
    public final class LoginActivity extends BaseActivity {
    }
    

    在kotlin中只允许覆写父类中加了open描述符的方法,这一点与类继承比较相似(类定义时只有加了open操作符才允许被继承);
    java中覆写方法时一般会加@override注解不加也可以通过编译,但是在kotlin中
    并且子类必须在覆写的方法上加上override,类似于java的@override,在java中不加也是可以通过编译的,但是在kotlin中子类覆写的方法必须加上override标注否则会报错

    //extends4.kt
    
    open class Base {
        open fun v() {}
        fun nv() {}
    }
    class Derived() : Base() {
        //覆盖
        fun v() {}
    }
    
    extends4.png

    加上override就可以正常编译了

    //extends5.kt
    open class Base {
        open fun v() {}
        fun nv() {}
    }
    class Derived() : Base() {
        override fun v() {}
    }
    

    kotlinc =>

    //Base.class
    public class Base
    {
      public void v() {}
      public final void nv() {}
    }
    
    //Derived.class
    public final class Derived extends Base {
      public void v() {}
    }
    
    

    Kotlin的类中声明可变的属性使用var关键字, 如果声明java中final的属性使用val关键字,kotlin编译的时候会为带var关键字的属性自动生成get和set方法,带val关键字的属性只会生成get方法

    //attr1.kt
    class Address {
        val name: String = "typ0520"
        var phone: String? = null
        var city: String = "sh"
        
        fun copyAddress(address: Address): Address {
            val result = Address() // Kotlin 中没有“new”关键字
            result.phone = address.phone // 将调用访问器
            result.city = address.city
            
            print("origin: ${this.name} current: ${address.name}")
            return result
        }
    }
    

    kotlinc =>

    //Address.class
    import kotlin.Metadata;
    import kotlin.jvm.internal.Intrinsics;
    import org.jetbrains.annotations.NotNull;
    import org.jetbrains.annotations.Nullable;
    
    public final class Address
    {
      @NotNull
      public final String getName() {
        return this.name;
      }
      
      @NotNull
      private final String name = "typ0520";
      @Nullable
      private String phone;
      
      @Nullable
      public final String getPhone() {
        return this.phone;
      }
      
      public final void setPhone(@Nullable String <set-?>) {
        this.phone = <set-?>;
      }
      
      @NotNull
      public final String getCity() {
        return this.city;
      }
      
      public final void setCity(@NotNull String <set-?>){
        Intrinsics.checkParameterIsNotNull(<set-?>, "<set-?>");
        this.city = <set-?>;
      }
      
      @NotNull
      private String city = "sh";
      
       @NotNull
      public final Address copyAddress(@NotNull Address address)
      {
        Intrinsics.checkParameterIsNotNull(address, "address");Address result = new Address();
        result.phone = address.phone;
        result.city = address.city;
        
        String str = address.name + " " + this.name;
        System.out.print(str);
        return result;
      }
    }
    
    

    这里有两个细节

    • setCity方法里第一行里会检查city是否为null Intrinsics.checkParameterIsNotNull(<set-?>, "<set-?>");
      是因为kotlin的属性默认是不允许为null的,如果某个属性允许为null,定义属性的时候需要在类型后面加上问号? swift也有可选这个特性不知道它俩谁抄谁的
    var phone: String? = null
    
    • copyAddress方法里对常量name的引用依然是this.name、address.name,如果在java中对常量的引用编译以后就会被优化成值copy,也就是说
      String str = address.name + " " + this.name
      这段会被编译成
      String str = "typ0520" + " " + "typ0520";

    如果对默认的get方法和set方法实现不满意,kotlin也提供了机制可以干预

    //attr2.kt
    class Address {
        var city: String = ""
        get() {
            return "city: ${field}"
        }
        set(_city) {
             //先别管field是啥玩意,后面会介绍
            field = _city
            print("city: ${field}")
        }
        
        fun copyAddress(address: Address): Address {
            val result = Address() // Kotlin 中没有“new”关键字
            result.city = address.city
           
             print("origin: ${this.name} current: ${address.name}")
            return result
        }
    }
    

    kotlinc =>

    //Address.class
    import java.io.PrintStream;
    import kotlin.Metadata;
    import kotlin.jvm.internal.Intrinsics;
    import org.jetbrains.annotations.NotNull;
    
    public final class Address {
      @NotNull
      private String city = "";
      
      @NotNull
      public final String getCity()
      {
        return "city: " + this.city;
      }
      
      public final void setCity(@NotNull String _city)
      {
        Intrinsics.checkParameterIsNotNull(_city, "_city");
        this.city = _city;
        String str = "city: " + this.city;
        System.out.print(str);
      }
      
      @NotNull
      public final Address copyAddress(@NotNull Address address)
      {
        Intrinsics.checkParameterIsNotNull(address, "address");Address result = new Address();
        result.setCity(address.getCity());
        
        return result;
      }
    }
    
    

    对比这两次编译出来的copyAddress方法中的内容,第一次对city的赋值使用的是

    result.city = address.city;
    

    第二次对city的赋值使用的是

    result.setCity(address.getCity());
    

    这点kotlin还是很严谨的,如果发现自定义了get和set的逻辑就会把直接引用属性的地方替换成调用get set方法,但是如果set{}和get{}代码块中引用了对应的属性这个替换就会出问题,我们来看下如果在set{}和get{}直接使用city字段会被编译成什么

    var city: String
    get() {
        return "city: ${city}"
    }
    set(_city) {
        city = _city
        print("city: ${city}")
    }
    

    kotlinc =>

    
    public final class Address {
      @NotNull
      public final String getCity() {
        return "city: " + getCity();
      }
      
      public final void setCity(@NotNull String _city) {
        Intrinsics.checkParameterIsNotNull(_city, "_city");
        setCity(_city);
        String str = "city: " + getCity();
        System.out.print(str);
      }
    }
    

    很明显setCity和getCity中都出现了死循环,kotlin为了解决这个问题提供了一个叫做field的幕后字段,仅在set{} get{}代码块中代替当前的属性

    接口

    kotlin中接口的方法是允许有默认实现的,但是java从1.8才开始支持接口默认实现,那么kotlin编译出来的class只能在1.8上运行?显然不太可能,我们来看下kotlin是怎么处理这个事情的

    //interface1.kt
    
    interface MyInterface {
        fun bar()
        fun foo() {
          // 可选的方法体
          print("MyInterface foo")
    
          this.bar()
        }
    }
    
    class Child : MyInterface {
        override fun bar() {
            // 方法体
        }
    }
    

    执行kotlinc以后会生成三个class文件

    interface1.png
    //MyInterface.class MyInterface$DefaultImpls.class
    
    public abstract interface MyInterface {
      public abstract void bar();
      public abstract void foo();
      
      public static final class DefaultImpls {
        public static void foo(MyInterface $this) {
          String str = "MyInterface foo";System.out.print(str);
          $this.bar();
        }
      }
    }
    
    //Child.class
    public final class Child implements MyInterface {
      public void bar() {}
      
      public void foo() {
        MyInterface.DefaultImpls.foo(this);
      }
    }
    

    kotlinc编译的时候如果发现接口中的某个方法有默认实现,就会生成一个以接口名+$+DefaultImpls方式命名的类,然后会创建一个同名的静态方法以容纳接口中对应方法的code,并且这个静态方法的参数列表中会多加一个名字叫$this类型是相应接口的变量,这样子类中这个方法的实现就可以直接调用这个静态方法了


    如果一个类实现了多个接口,就有可能出现接口中方法冲突的问题,可以通过super<XXX>语法来选择对应父类的方法

    //interface2.kt
    
    interface A {
        fun foo() { print("A") }
        fun bar()
    }
    
    interface B {
        fun foo() { print("B") }
        fun bar() { print("bar") }
    }
    
    class D : A, B {
        override fun foo() {
            super<A>.foo()
            super<B>.foo()
        }
    
        override fun bar() {
            super<B>.bar()
        }
    }
    

    kotlinc =>

    //D.class
    
    public final class D implements A, B {
      public void foo() {
        A.DefaultImpls.foo(this);
        B.DefaultImpls.foo(this);
      }
      
      public void bar() {
        B.DefaultImpls.bar(this);
      }
    }
    
    

    扩展

    kotlin提供了一种类似于objective-c category的扩展功能,是继承的一个有力补充,作用主要有下面这两点

    1、代替一些在java中需要util类辅助来完成的操作
    举个例子我们需要对ArrayList中的元素按位置做交换,在java中通常我们会写一个静态方法放在某个util类中

    //Utils.java
    
    public class Utils {
        public static void swap(List<Integer> list,int index1,int index2) {
            int index1Val = list.get(index1);
            int index2Val = list.get(index2);
    
            list.remove(index1);
            list.add(index1,index2Val);
    
            list.remove(index2);
            list.add(index2,index1Val);
        }
    }
    

    在kotlin中可以这样写

    //extension.kt
    
    fun MutableList<Int>.swap(index1: Int, index2: Int) {
        val tmp = this[index1]
        this[index1] = this[index2]
        this[index2] = tmp
    }
    
    fun hello() {
        val list = mutableListOf(1, 2, 3)
        list.swap(0, 2) // “swap()”内部的“this”得到“l”的值
    }
    

    可以当做MutableList直接有这个swap这个方法,这样比Util类用起来更加符合面向对象的思维

    我们来编译下这段kotlin代码

    //ExtensionKt.class
    
    public final class ExtensionKt
    {
      public static final void swap(@NotNull List<Integer> $receiver, int index1, int index2)
      {
        Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
        int tmp = ((Number)$receiver.get(index1)).intValue();
        $receiver.set(index1, $receiver.get(index2));
        $receiver.set(index2, Integer.valueOf(tmp));
      }
      
      public static final void hello()
      {
        List list = CollectionsKt.mutableListOf(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) });
        swap(list, 0, 2);
      }
    }
    
    

    可以看出编译成class后还是使用静态方法实现的,并不是真正的改变了List的类结构(如果改变了类结构应该是list.swap(0, 2))

    2、给一些方法设置别名

    玩过java的肯定都知道Object中的toString方法,如果你以前是玩objective-c的应该还是习惯叫description,那么你就可以通过扩展来给toString设置别名

    //extension2.kt
    
    fun Any.description() : String {
        return this.toString()
    }
    
    fun hello() {
        val str = "xxxx"
        str.description()
    }
    
    

    处于安全性的考虑扩展的方法是不允许和类中已有的方法冲突的,同时也不允许多个跟对同一个类的扩展中包含相同的方法,来看下两个例子

    class C {
        fun foo() { println("member") }
    }
    
    fun C.foo() { println("extension") }
    

    kotlin =>

    extension3.png
    class C {
        fun foo() { println("member") }
    }
    
    fun C.foo2() { println("foo2") }
    fun C.foo2() { println("foo2-2") }
    

    kotlin =>

    extension4.png

    kotlin允许在一个类内部为另一个类声明扩展

    //extension5.kt
    
    class D {
        fun bar() {
            println("D.bar")
        }
    
        fun hello() {
            println("D hello")
        }
    }
    
    class C {
        fun baz() {
            println("D.baz")    
        }
    
        fun hello() {
            println("C hello")
        }
    
        fun D.foo() {
            bar()   // 调用 D.bar
            baz()   // 调用 C.baz
    
             //重点看这个这里
            hello()
        }
    
        fun caller(d: D) {
            d.foo()   // 调用扩展函数
        }
    }
    
    fun main(args: Array<String>) {
        val d = D()
        val c = C()
        c.caller(d)
    }
    

    在D.foo扩展方法的作用域里存在两个this,一个是扩展声明所在的类C、一个是被扩展类D的。调用bar方法和baz方法没什么问题,但是hello方法既存在于C又存在于D,那么kotlin会选择调用那个hello方法呢(这段代码是可以通过编译的)?我们来编译运行下看下输出

    kotlinc extension5.kt -include-runtime -d extension5.jar;java -jar extension5.jar
    =>

    extension5.png

    从输出的日志上可以看出调用的是被扩展类D的hello方法,我们再看下extension5.jar里的class内容

    //C.class
    
    public final class C
    {
      public final void baz() {
        String str = "D.baz";
        System.out.println(str);
      }
      
      public final void hello() {
        String str = "C hello";
        System.out.println(str);
      }
      
      public final void foo(@NotNull D $receiver) {
        Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
        $receiver.bar();
        baz();
        
        $receiver.hello();
      }
      
      public final void caller(@NotNull D d) {
        Intrinsics.checkParameterIsNotNull(d, "d");foo(d);
      }
    }
    
    

    如果想调用C类的hello方法,可以使用this@C.hello()

    //extension6.kt
    
    class D {
        fun hello() {
            println("D hello")
        }
    }
    
    class C {
        fun hello() { println("C hello") }
        fun D.foo() { 
            this@C.hello() 
        }
        fun caller(d: D) { d.foo() }
    }
    
    fun main(args: Array<String>) {
        val d = D()
        val c = C()
        c.caller(d)
    }
    

    =>

    //C.class
    
    public final class C {
      public final void hello() {
        String str = "C hello";
        System.out.println(str);
      }
      
      public final void foo(@NotNull D $receiver) {
        Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
        hello();
      }
      
      public final void caller(@NotNull D d) {
        Intrinsics.checkParameterIsNotNull(d, "d");foo(d);
      }
    }
    

    除了可以扩展方法,kotlin还允许扩展属性

    //extension7.kt
    
    val <T> List<T>.lastIndex: Int
        get() = size - 1
    
    fun main(args: Array<String>) {
        val list = listOf(1,2,3,4)
    
        println(list.lastIndex)
    }
    

    kotlinc extension7.kt -include-runtime -d extension7.jar;java -jar extension7.jar

    =>

    extension7.png
    public final class Extension7Kt {
      public static final <T> int getLastIndex(@NotNull List<? extends T> $receiver) {
        Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
        return $receiver.size() - 1;
      }
      
      public static final void main(@NotNull String[] args) {
        Intrinsics.checkParameterIsNotNull(args, "args");
        List list = CollectionsKt.listOf(new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3), Integer.valueOf(4) });
        int i = getLastIndex(list);
        System.out.println(i);
      }
    }
    

    可以看出所谓的属性扩展和方法扩展一样都是用静态方法实现的

    数据类

    在java开发中充斥着大量的model类,除了属性定义其它的基本上都是一些模板代码,虽然ide能帮我们生成这些代码,看着还是很不爽的,kotlin提供了一中叫数据类的玩意,编译器自动从主构造函数中声明的所有属性导出以下成员:

    • equals()/hashCode()
    • toString() 格式是 "User(name=John, age=42)"
    • componentN() 函数 按声明顺序对应于所有属性
    • copy() 函数
    //data-class.kt
    
    data class User(val name: String, val age: Int)
    

    kotlinc =>

    public final class User {
      @NotNull
      private final String name;
      private final int age;
      
      public boolean equals(Object paramObject) {
        if (this != paramObject)
        {
          if ((paramObject instanceof User))
          {
            User localUser = (User)paramObject;
            if (Intrinsics.areEqual(this.name, localUser.name)) {
              if ((this.age == localUser.age ? 1 : 0) == 0) {}
            }
          }
        }
        else {
          return true;
        }
        return false;
      }
      
      /* Error */
      public int hashCode() {
        //jd 反编译有时候会出错,凑合着看吧 ^_^
        // Byte code:
        //   0: aload_0
        //   1: getfield 11 User:name   Ljava/lang/String;
        //   4: dup
        //   5: ifnull +9 -> 14
        //   8: invokevirtual 63    java/lang/Object:hashCode   ()I
        //   11: goto +5 -> 16
        //   14: pop
        //   15: iconst_0
        //   16: bipush 31
        //   18: imul
        //   19: aload_0
        //   20: getfield 19    User:age    I
        //   23: iadd
        //   24: ireturn
      }
      
      public String toString() {
        return "User(name=" + this.name + ", age=" + this.age + ")";
      }
      
      @NotNull
      public final User copy(@NotNull String name, int age) {
        Intrinsics.checkParameterIsNotNull(name, "name");
        return new User(name, age);
      }
      
      public final int component2() {
        return this.age;
      }
      
      @NotNull
      public final String component1() {
        return this.name;
      }
      
      public User(@NotNull String name, int age) {
        this.name = name;this.age = age;
      }
      
      public final int getAge() {
        return this.age;
      }
      
      @NotNull
      public final String getName() {
        return this.name;
      }
    }
    
    

    嵌套类

    与java类似kotlin的类定义也是允许嵌套的,我们分别看下java中的静态内部类、内部类、匿名内部类对应kotlin的语法

    • 静态内部类
    //nested-classes1.kt
    
    class Outer {
        private val bar: Int = 1
        class Nested {
            fun foo() = 2
        }
    }
    
    val demo = Outer.Nested().foo() // == 2
    

    kotlinc =>

    //Outer.class
    public final class Outer {
      private final int bar = 1;
      
      public static final class Nested {
        public final int foo() {
          return 2;
        }
      }
    }
    
    //Nested_classes1Kt.class
    public final class Nested_classes1Kt {
      public static final int getDemo() {
        return demo;
      }
      
      private static final int demo = new Outer.Nested().foo();
    }
    
    
    • 内部类
    //nested-classes2.kt
    
    class Outer {
        private val bar: Int = 1
        
        fun hello() {
            print("hello")
        }
        
        inner class Inner {
            fun foo() -> Int {
                return bar
            }
            
            fun callHello() {
                hello()
            }
        }
    }
    
    val demo = Outer().Inner().foo() // == 1
    

    kotlinc =>

    //Outer.class
    public final class Outer {
      private final int bar = 1;
      
      public final void hello() {
        String str = "hello";
        System.out.print(str);
      }
      
      public final class Inner {
        public final int foo() {
          return Outer.access$getBar$p(this.this$0);
        }
        
        public final void callHello() {
          this.this$0.hello();
        }
      }
    }
    
    //
    //Nested_classes2Kt.class
    
    public final class Nested_classes2Kt {
      public static final int getDemo() {
        return demo;
      }
      
      private static final int demo = new Outer.Inner(new Outer()).foo();
    }
    
    
    • 匿名内部类
    //nested-classes3.kt
     class View {
        fun setOnClickListener(listener: OnClickListener) {
            print("${listener}")
        }
    
        class OnClickListener {
            fun onClick(view: View) {
            
            }
        }
    }
    
    fun hello() {
        val v = View()
        v.setOnClickListener(object: View.OnClickListener() {
            override fun onClick(view: View) {
            
            }
        })
    }
    

    kotlinc =>

    //View.class
    
    public class View {
      public final void setOnClickListener(@NotNull View.OnClickListener listener) {
        Intrinsics.checkParameterIsNotNull(listener, "listener");
        String str = String.valueOf(listener);
        System.out.print(str);
      }
      
      public static class OnClickListener {
        public void onClick(@NotNull View view) {
          Intrinsics.checkParameterIsNotNull(view, "view");
        }
      }
    }
    
    public final class Nested_classes3Kt {
      public static final void hello() {
        View v = new View();
        v.setOnClickListener((View.OnClickListener)new View.OnClickListener() {
          public void onClick(@NotNull View view) {
            Intrinsics.checkParameterIsNotNull(view, "view");
          }
        });
      }
    }
    
    public final class Nested_classes3Kt$hello$1 extends View.OnClickListener {
      public void onClick(@NotNull View view) {
        Intrinsics.checkParameterIsNotNull(view, "view");
      }
    }
    
    

    匿名类编译的时候会生成一个子类容纳被覆盖的方法以及增加的属性,因此类定义的时候必须要加上open。


    这次先说这么多,其它的语法编译的分析等有时间的时候在补上。未完待续~~

    相关文章

      网友评论

      • 春江潮:class Person {
        init() {
        print("Person initialized ${firstName}")
        }

        constructor(firstName: String) {
        print("constructor1: ${firstName}")
        }

        constructor(firstName: String,secondName: String) {
        print("constructor2 ${firstName} ${secondName}")
        }
        }
        这块编译不通过是因为构造方法里面的变量firstName是局部变量不是成员变量。而init{}中调用的变量是成员变量。
        typ0520:感谢指正,当时对kotlin语法不是特别熟悉,这个类其实可以这样定义
        class Person(val firstName: String, val secondName: String = null)
      • typ0520:@KwokKwok 谢谢支持:stuck_out_tongue_winking_eye:,kotlin还是挺优雅的
      • b0542f6c4d28:有没有用kotlin写的比较好的代码
        感觉kotlin写东西 思维方式和java都不一样
        b0542f6c4d28: @KwokKwok 这个需要as3.0环境 我目前还是2.3 用不了
        KwokKwok:我推荐一个吧,https://github.com/googlesamples/android-topeka,Google的一个案例,代码质量绝对不低。逛play的时候发现的,设计、功能实现感觉都比较好,一看是开源的,clone下来运行一看,还是kotlin写的,就因为这个我才找kotlin的文章看。
        typ0520:@那么好了 github上有用kotlin写的app,可以看看
      • KwokKwok:作者很用心,感谢!特意起床打开电脑来点赞的,哈哈,写的很好。确实长,很实在,晚上断断续续看了一个小时。不过看样子热度不是很高,估计很多人因为觉得文章长,可惜了。好久没碰Android了,之前还简单用kotlin写过点东西,也忘得一干二净了,不过心里倒是一直觉得kotlin语法挺奇怪,念念不忘,哈哈。
      • 有点健忘:kotlinc 命令哪来的哈,kotlin就是个插件,也没有环境变量一说,这命令找不到。:joy:
        typ0520:@有点健忘 你这个是用idea反编译的它会尝试展示kotlin代码的,要用jd去反编译class
        有点健忘:@typ0520 谢谢,我百度找到了。运行kotlinc命令,我咋生成的class文件和你的不一样,我的class文件里没关键代码,如下我编译的那个hello方法,就是里边包的when 方法的那个
        // IntelliJ API Decompiler stub source generated from a class file
        // Implementation of methods is not available

        package sage.tinkerdemo.kotlin

        public fun hello(x: kotlin.Any): kotlin.Unit { /* compiled code */ }
        typ0520:需要单独安装编译器,可以通过SDKMAN、brew、MacPorts进行安装,参考这篇文章
        https://kotlinlang.org/docs/tutorials/command-line.html
        也可以直接下载一个zip包配置一些环境变量,相当于jdk可以单独安装
        https://github.com/JetBrains/kotlin/releases/tag/v1.1.2-5
        https://github.com/JetBrains/kotlin/releases/download/v1.1.2-5/kotlin-compiler-1.1.2-5.zip

      本文标题:kotlin-jvm编译过程(一)

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