文档结构  
翻译进度:已翻译     翻译赏金:10 元 (?)    ¥ 我要打赏

介绍

到底什么才是 “JVM 语言”?能在 JVM 上运行的语言难道不就应该是只有 Java 才对吗?

Kotlin 提供了诸多 Java 不支持的特性,比如 函数类型(function type)、扩展函数(extension function)数据类(data class)。这些都是如何办到的?我仔细研究了一下 Kotlin 实现它们的方式,同时也对 “JVM 语言” 这个概念有了更深的理解。我们需要了解下 Kotlin 的字节码生成方式,希望本文能帮助读者在相关方面多一些深入理解。

关于 Kotlin 语言特性的一些更多细节,你可以参考下我最近的文章,比如这篇

第 1 段(可获 1.31 积分)

Java 虚拟机

一句话定义就是:Java 虚拟机的作用是用来运行 Java 字节码

当然,对这个复杂的工具进行详细描述的话,可谓一言难尽,Oracle 有一份规范说明来做这样的描述。

你可能已经了解,JVM 就是一个能在不同操作系统中运行的虚拟机。本质上,正是由于 JVM 成为执行代码和操作系统之间的抽象层,才使得 Java 具有“平台独立性”。

JVM 和真实的计算机一样提供了一组预定义的指令给程序使用;而这些指令在执行的时候又会被 JVM 翻译为机器指令,来完成实际操作。

第 2 段(可获 1.43 积分)

正如 JVM 规范描述,Java 虚拟机并不知道 Java编程语言的任何事情。不过,它定义了二进制格式的 class,这是一个含有机器指令(=字节码)的文件,用于执行 (除开更多信息)。这一点很有趣,因为它实际上表示:

  1. JVM 并非只致力于 Java 编程语言。
  2. 你可以自由地选择创建 JVM 程序的技术,唯一的要求是能提供适当却遵从严格约束的 class 文件。
  3. 不管什么编程语言,生成的 Java 字节码可以与 JVM 中的其它 Java 字节码交互。
第 3 段(可获 1.31 积分)

创建类文件

从人类可读的源代码创建类文件的过程就是编译器干的事情。Oracle 随 JDK 发布的 Java 编译器 (java) 就是一个示例,它能把 .java 文件编译为 .class 文件。

除了 Java,过去几年出现了很多其它 JVM 语言,它们旨在为我们开发者提供另一种抽象来创建  JVM 程序。

Kotlin 就是这些语言中的一种。

Kotlin 字节码生成

官方 FAQ 中陈述“Kotlin 产生与 Java 兼容的字节码”,这表示 Kotlin 编译器有能力将所有漂亮的特性转换 JVM 兼容指令,这甚至可以使用 IntelliJ IDEA 工具观察到。

第 4 段(可获 1.48 积分)

来看一些示例:

顶级函数

Kotlin

//File.kt
fun foobar(){}

这个定义在 .kt 文件中简单的顶级函数可用 IntelliJ 查看:“工具(Tools) → Kotlin → 显示 Kotlin 字节码” 会在 IDE 中打印一个新窗口,实时预览编辑器会为当前编译的 .kt 文件创建的 Java 字节码。

Java 字节码

public final class de/swirtz/kotlin/FileKt {
  // access flags 0x19
  public final static foobar()V
   L0
    LINENUMBER 3 L0
    RETURN
   L1
    MAXSTACK = 0
    MAXLOCALS = 0

  @Lkotlin/Metadata;
  // compiled from: File.kt
}
第 5 段(可获 0.73 积分)

恐怕只有少部分人能看懂这些文件,因此我们还可以选择 “反编译”。之后,我们展示一个 Java 类,它与前面 Kotlin 描述的功能相同:

反编译的顶级函数

public final class FileKt {
   public static final void foobar() {
   }
}

看到这个你可能已经知道,Kotlin 顶级类被会编译成 Java 的 final 类,它有一个静态函数 (这个结构看起来像扩展函数,或者称为:实用工具类)。再来看一个难一些的示例:

第 6 段(可获 1.05 积分)

类和扩展函数

Kotlin

class MyClass(val i: Int)

fun MyClass.myExtension(value: String) = value.length

这个示例显示了一个简单的类 MyClass,它有一个 Int 类型的属性,以及一个顶级的扩展函数。

首先,我们应该看看这个类会编译成什么,这很有趣,因为我们在这里使用主构造器val 关键字

Java 类

public final class MyClass {
   private final int i;

   public final int getI() {
      return this.i;
   }

   public MyClass(int i) {
      this.i = i;
   }
}
第 7 段(可获 0.68 积分)

正如我们所期望的:属性是一个 final 成员,在唯一的构造器中赋值。诚然,它在 Kotlin 中是如何简单。

反编译出来的扩展函数

public final class FileKt {
   public static final int myExtension(@NotNull MyClass $receiver, @NotNull String value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      Intrinsics.checkParameterIsNotNull(value, "value");
      return value.length();
   }
}

在 Java 代码中,这个扩展函数本身被编译成静态方法,有一个 receiver 对象作为参数。

第 8 段(可获 0.55 积分)

从这个例子中我们能看到这个叫Intrinsics的类的使用.这是Kotlin stdlib库的一部分,并且正因为参数不能为null它才被用上。让我们看看如果将初始扩展函数的参数更改为value类型会发生什么: String? 而且,访问的长度保证在一个安全的范围内。

反编译的可为空参数的扩展函数

public final class FileKt {
   @Nullable
   public static final Integer myExtension(@NotNull MyClass $receiver, @Nullable String value) {
      Intrinsics.checkParameterIsNotNull($receiver, "$receiver");
      return value != null?Integer.valueOf(value.length()):null;
   }
}
第 9 段(可获 0.81 积分)

我们不再需要对 value 进行检查,因为已经告诉编译器它允许为 null

下面这个例子有一点点棘手了。这也是 Kotlin 和 Java 最大的不同:

Ranges  

Kotlin

fun loopWithRange(){
    for(i in 5 downTo 1 step 2){
        print(i)
    }
}

反编译

 public static final void loopWithRange() {
      IntProgression var10000 = RangesKt.step(RangesKt.downTo(5, 1), 2);
      int i = var10000.getFirst(); //i: 5
      int var1 = var10000.getLast(); //var1: 1
      int var2 = var10000.getStep(); //var2: -2
      if(var2 > 0) {
         if(i > var1) {
            return;
         }
      } else if(i < var1) {
         return;
      }

      while(true) {
         System.out.print(i);
         if(i == var1) {
            return;
         }

         i += var2;
      }
   }
第 10 段(可获 0.53 积分)

虽然Java代码仍然是完全可以理解的,但可能没人会在现实生活中写它,因为用一种简单的方式我们同样可以做到。我们需要想到表面上看起来downTo和step是中缀表示法,实际上它们却是函数调用。 为了提供这种灵活性,稍微多一点的代码似乎是必要的。

第 11 段(可获 0.86 积分)

结论

我认为,大多数时候我们并不在乎Kotlin编译器为我们生产什么。 然而,我发现观察它非常有趣和有用,因为它支持以某种方式回答我的初始问题。当然,Kotlin不仅仅是抽象Java的运算符,因为它还为现有的Java类提供了如此多的扩展,例如List或String。
不过,我们也看到,有时编译的Java代码比它必须更详细。 这会影响性能? 是的,确实有轻微的影响。看看Dmitry Jemerov的这个演示文稿 ,如果你对更多的“Kotlin→Java字节码”的例子感兴趣,这些例子也考虑到性能。

最后,如果你想了解关于Kotlin的功能,我推荐你的书Kotlin in Action

让我知道你是怎么想的,如果你愿意,可以和我联系

 - Simon

第 12 段(可获 1.94 积分)

文章评论