转载

kotlinc vs javac

对比一下 java 和 kotlin 编译源码文件(source.java 或 source.kt) 到 class 的区别。这种区别产生的结果是什么,看看其中的优缺点。

javac

public class Hello {
    public static void main(String[] args) {
      System.out.println("hello world");
    }
}

以上代码执行 javac Hello.java 将生成 Hello.class 文件,用 javap 命令对生成的 Hello.class 文件做一下解析,得到结果

Hello.clsss 解析后的信息

点击展开
Classfile /Users/mac/Desktop/javadir/Hello.class
  Last modified 2019-8-24; size 415 bytes
  MD5 checksum cb7981a0b53212d3f3005746f14b245a
  Compiled from "Hello.java"
public class Hello
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#15         // java/lang/Object."<init>":()V
   #2 = Fieldref           #16.#17        // java/lang/System.out:Ljava/io/PrintStream;
   #3 = String             #18            // hello world
   #4 = Methodref          #19.#20        // java/io/PrintStream.println:(Ljava/lang/String;)V
   #5 = Class              #21            // Hello
   #6 = Class              #22            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               main
  #12 = Utf8               ([Ljava/lang/String;)V
  #13 = Utf8               SourceFile
  #14 = Utf8               Hello.java
  #15 = NameAndType        #7:#8          // "<init>":()V
  #16 = Class              #23            // java/lang/System
  #17 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #18 = Utf8               hello world
  #19 = Class              #26            // java/io/PrintStream
  #20 = NameAndType        #27:#28        // println:(Ljava/lang/String;)V
  #21 = Utf8               Hello
  #22 = Utf8               java/lang/Object
  #23 = Utf8               java/lang/System
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Utf8               java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/String;)V
{
  public Hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 1: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #3                  // String hello world
         5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8
}
SourceFile: "Hello.java"

可以得到一些关键信息:

  • 生成的 .class 文件大小 415 bytes
  • .class 文件中除了 main 方法的指令之外,还默认添加了一个 Hello 类的构造函数,即 init 方法。

kotlinc

下面看看,用 Kotlin 语言实现同样的功能,然后编译之后的.class 文件又是怎样的。

kotlinc

安装 kotlinc

命令行输入

brew install kotlin

Hello1.kt

object Hello1 {
    @JvmStatic
    fun main(args: Array<String>) {
        println("hello world")
    }
}

这段代码和上述 Hello.java 是实现了同样的功能,方法参数也是一样。

以上代码执行 kotlinc Hello1.kt 后将生成 Hello1.class 文件,用 javap 命令对生成的 Hello1.class 文件做一下解析,得到结果

Hello1.clsss 解析后的信息

点击展开
Classfile /Users/mac/Desktop/javadir/Hello1.class
  Last modified 2019-8-24; size 1236 bytes
  MD5 checksum 884060fdf34740300f1c8d187afb4c6a
  Compiled from "Hello1.kt"
public final class Hello1
  minor version: 0
  major version: 50
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
   #1 = Utf8               Hello1
   #2 = Class              #1             // Hello1
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               main
   #6 = Utf8               ([Ljava/lang/String;)V
   #7 = Utf8               Lkotlin/jvm/JvmStatic;
   #8 = Utf8               Lorg/jetbrains/annotations/NotNull;
   #9 = Utf8               args
  #10 = String             #9             // args
  #11 = Utf8               kotlin/jvm/internal/Intrinsics
  #12 = Class              #11            // kotlin/jvm/internal/Intrinsics
  #13 = Utf8               checkParameterIsNotNull
  #14 = Utf8               (Ljava/lang/Object;Ljava/lang/String;)V
  #15 = NameAndType        #13:#14        // checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
  #16 = Methodref          #12.#15        // kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
  #17 = Utf8               hello world
  #18 = String             #17            // hello world
  #19 = Utf8               java/lang/System
  #20 = Class              #19            // java/lang/System
  #21 = Utf8               out
  #22 = Utf8               Ljava/io/PrintStream;
  #23 = NameAndType        #21:#22        // out:Ljava/io/PrintStream;
  #24 = Fieldref           #20.#23        // java/lang/System.out:Ljava/io/PrintStream;
  #25 = Utf8               java/io/PrintStream
  #26 = Class              #25            // java/io/PrintStream
  #27 = Utf8               println
  #28 = Utf8               (Ljava/lang/Object;)V
  #29 = NameAndType        #27:#28        // println:(Ljava/lang/Object;)V
  #30 = Methodref          #26.#29        // java/io/PrintStream.println:(Ljava/lang/Object;)V
  #31 = Utf8               [Ljava/lang/String;
  #32 = Utf8               <init>
  #33 = Utf8               ()V
  #34 = NameAndType        #32:#33        // "<init>":()V
  #35 = Methodref          #4.#34         // java/lang/Object."<init>":()V
  #36 = Utf8               this
  #37 = Utf8               LHello1;
  #38 = Utf8               INSTANCE
  #39 = Utf8               <clinit>
  #40 = Utf8               Lkotlin/Metadata;
  #41 = Utf8               mv
  #42 = Integer            1
  #43 = Integer            15
  #44 = Utf8               bv
  #45 = Integer            0
  #46 = Integer            3
  #47 = Utf8               k
  #48 = Utf8               d1
  #49 = Utf8               /n/n/n/n/n/n/n/nÆ20B¢J0200H¢
  #50 = Utf8               d2
  #51 = Utf8
  #52 = Methodref          #2.#34         // Hello1."<init>":()V
  #53 = NameAndType        #38:#37        // INSTANCE:LHello1;
  #54 = Fieldref           #2.#53         // Hello1.INSTANCE:LHello1;
  #55 = Utf8               Hello1.kt
  #56 = Utf8               Code
  #57 = Utf8               LineNumberTable
  #58 = Utf8               LocalVariableTable
  #59 = Utf8               RuntimeVisibleAnnotations
  #60 = Utf8               RuntimeInvisibleParameterAnnotations
  #61 = Utf8               SourceFile
  #62 = Utf8               SourceDebugExtension
{
  public static final Hello1 INSTANCE;
    descriptor: LHello1;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  public static final void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=2, locals=3, args_size=1
         0: aload_0
         1: ldc           #10                 // String args
         3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
         6: ldc           #18                 // String hello world
         8: astore_1
         9: iconst_0
        10: istore_2
        11: getstatic     #24                 // Field java/lang/System.out:Ljava/io/PrintStream;
        14: aload_1
        15: invokevirtual #30                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
        18: return
      LineNumberTable:
        line 4: 6
        line 5: 18
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      19     0  args   [Ljava/lang/String;
    RuntimeVisibleAnnotations:
      0: #7()
    RuntimeInvisibleParameterAnnotations:
      0:
        0: #8()

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=1, args_size=0
         0: new           #2                  // class Hello1
         3: dup
         4: invokespecial #52                 // Method "<init>":()V
         7: astore_0
         8: aload_0
         9: putstatic     #54                 // Field INSTANCE:LHello1;
        12: return
      LineNumberTable:
        line 1: 0
}
SourceFile: "Hello1.kt"
SourceDebugExtension:
  SMAP
  Hello1.kt
  Kotlin
  *S Kotlin
  *F
  + 1 Hello1.kt
  Hello1
  *L
  1#1,6:1
  *E
RuntimeVisibleAnnotations:
  0: #40(#41=[I#42,I#42,I#43],#44=[I#42,I#45,I#46],#47=I#42,#48=[s#49],#50=[s#37,s#51,s#33,s#5,s#51,s#9,s#51,s#51,s#6])

可以得到一些关键信息:

  • 首先最直观的一点,这个 class 文件的信息变多了
  • Hello1.class 文件的信息,达到了 1236 bytes
  • 多出来的信息,主要是 SourceDebugExtension,RuntimeVisibleAnnotations,RuntimeInvisibleParameterAnnotations 和 LocalVariableTable(本地变量表的信息)
  • 构造函数的 init 基本相似,变化不大
  • 常量池 Constant Pool 变大了近乎一倍之多。
  • main 方法由于要做参数检测,也是变大了许多。

总结

Hello.class vs Hello1.class

最终直观感受一下:

-rw-r--r--   1 mac  staff   415  8 24 09:37 Hello.class
-rw-r--r--@  1 mac  staff   114  8 24 08:37 Hello.java
-rw-r--r--   1 mac  staff  1236  8 24 09:37 Hello1.class
-rw-r--r--   1 mac  staff    92  8 24 09:34 Hello1.kt

用 kotlin 实现和 java 代码相同的功能,kotlin 在源文件上是占优的,这也是 kotlin 的优势,代码简洁,但是产生的代价就是 class 文件的变大(当然这里的为了实现 static 的 main 方法,使用了类似单例的写法,可能不太恰当,但是总体趋势是类似的)

扩展

我们知道 javac 编译生成的 xxx.class 文件是可以通过 java xxx 直接运行的,那么 kotlinc 生成的 yyy.class 文件改怎么运行呢?可以直接用 java yyy 吗?我们试一下

──> java Hello1                                                 ──(六, 824)─┘
Exception in thread "main" java.lang.NoClassDefFoundError: kotlin/jvm/internal/Intrinsics
	at Hello1.main(Hello1.kt)

结果是不行的,虽然源码可以互相调用,但是 class 文件还的各来各的。因此需要用 kotlin yyy

──> kotlin Hello1                                           1 ↵ ──(六, 824)─┘
hello world
原文  https://rebooters.github.io/2019/08/24/kotlinc-vs-javac/
正文到此结束
Loading...