美文网首页
Scala:HelloWorld

Scala:HelloWorld

作者: tracy_668 | 来源:发表于2021-01-20 22:18 被阅读0次

    [TOC]
    Scala也是一种运行于Java虚拟机上的语言, 既然能够运行于虚拟机之上, 那么它必然可以编译成class文件, 因为虚拟机只认class文件。 所以, scalac编译器将.scala源文件, 编译成class文件, 然后这些class文件被虚拟机加载并执行。


    image.png

    所以, 如果你对class文件格式和java虚拟机足够了解的话, 那么学习scala语言就会相对简单。Java文件编译成class文件, 而Scala源文件也是编译成class文件, 虽然他们语法大相径庭, 但是最后殊途同归。 如果我们能基于class文件分析scala的行为, 有助于理解scala语言。有人说scala的语法很多很难, 其实语法终归是写法, class文件的格式是不变的, 可以把scala的语法看成java语法的高级语法糖

    Scala的HelloWorld

    按照IT界的传统, 下面我们就从HelloWorld开始分析。 下面是scala版的HelloWorld源码:

    object HelloWorld{
        def main(args : Array[String]){
            println("HelloWorld")
        }
    }
    

    1 以object关键字修饰一个类名, 这种语法叫做孤立对象,这个对象是单例的。 相当于将单例类和单例对象同时定义。

    2 方法声明以def开头, 然后是方法名, 参数列表, 返回值, 等号, 方法体 。如下:

    def doSomeThing(x : Int) : Int = {
        x += 1
    }
    

    如果没有返回值, 可以省略等号, 直接写方法体。

    3 Array[String]是scala的一种数据类型, 可以理解为字符串数组。

    反编译scala HelloWorld

    我们所说的反编译, 是指使用javap工具反编译class文件, 所以, 在反编译之前, 要先使用scalac编译器编译该源文件:

    scalac HelloWorld.scala

    命令执行完成后, 可以看到HelloWorld.scala所在的目录中多出两个class文件:

    HelloWorld$.class       HelloWorld.class
    
    

    其中有一个是和HelloWorld.scala对应的HelloWorld.class 。 那么HelloWorld$.class是什么呢?难道一个scala类可以生成多个class吗? 下面通过反编译来找到答案。

    首先反编译HelloWorld.class :

    javap -c -v -classpath . HelloWorld
    反编译结果如下: (为了便于讲述, 给出了所有的输出, 会有些长)

     Last modified 2021-1-20; size 587 bytes
      MD5 checksum 62bc1cdda24f9f338e0f467004f66eda
      Compiled from "HelloWorld.scala"
    public final class scala.HelloWorld
      minor version: 0
      major version: 50
      flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
    Constant pool:
       #1 = Utf8               scala/HelloWorld
       #2 = Class              #1             // scala/HelloWorld
    h\tq\"= Utf8               java/lang/Object
       #9 = Utf8               main           // java/lang/Object
      #10 = Utf8               ([Ljava/lang/String;)V
      #11 = Utf8               scala/HelloWorld$alaSignature;
      #12 = Class              #11            // scala/HelloWorld$
      #13 = Utf8               MODULE$\t!S3mY><vN7e!B:dC2
      #14 = Utf8               Lscala/HelloWorld$;       7\t\riA\tA\t!
      #15 = NameAndType        #13:#14        // MODULE$:Lscala/HelloWorld$;e2$7CA
      #16 = Fieldref           #12.#15        // scala/HelloWorld$.MODULE$:Lscala/HelloWorld$;I\=SKDQAD=\taP5oSRtD#A\t
      #17 = NameAndType        #9:#10         // main:([Ljava/lang/String;)V                                          E9A\n\t5
      #18 = Methodref          #12.#17        // scala/HelloWorld$.main:([Ljava/lang/String;)V                                N
      #19 = Utf8               Code                                                                                            'Y\"A
      #20 = Utf8               SourceFile                                                                                           \nU!B+oSRDQa\tAa\tA!:hgBa!G\┼␋!!B!⎽␊C \t1Q$1  #21 = Utf8               RuntimeVisibleAnnotations
      #22 = Utf8               ScalaSig
    {
      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     #16                 // Field scala/HelloWorld$.MODULE$:Lscala/HelloWorld$;
             3: aload_0
             4: invokevirtual #18                 // Method scala/HelloWorld$.main:([Ljava/lang/String;)V
             7: return
    }
    SourceFile: "HelloWorld.scala"
    RuntimeVisibleAnnotations:
      0: #6(#7=s#8)
    Error: unknown attribute
      ScalaSig: length = 0x3
       05 00 00
    
    

    从输出结果可以看到, 这个类确实有传统意义上的main方法。 这个main方法中的字节码指令大概是这样:
    1 getstatic访问一个静态字段(获取类的静态字段,将其值压入栈顶
    ), 这个静态字段是定义在HelloWorld类中的MODULE字段, 这个字段的类型是HelloWorld$ 。 讲到这里, 大概出现了单例的影子。 我们并没有定义这个类, 所以这个类是scala编译器自动生成的, 用来辅佐HelloWorld类。

    2 然后使用这个静态对象调用main方法, 这个main方法是HelloWorld$类中的, 而不是当前HelloWorld中的。 它不是静态的, 而是成员方法。

    下面反编译HelloWorld$类:

    javap -c -v -classpath . HelloWorld$
    
      Last modified 2021-1-20; size 646 bytes
      MD5 checksum 9d052c3ccb368bf74eb34efdd105c50a
      Compiled from "HelloWorld.scala"
    public final class scala.HelloWorld$
      minor version: 0
      major version: 50
      flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
    Constant pool:
       #1 = Utf8               scala/HelloWorld$
       #2 = Class              #1             // scala/HelloWorld$
       #3 = Utf8               java/lang/Object
       #4 = Class              #3             // java/lang/Object
       #5 = Utf8               HelloWorld.scala
       #6 = Utf8               MODULE$
       #7 = Utf8               Lscala/HelloWorld$;
       #8 = Utf8               <clinit>
       #9 = Utf8               ()V
      #10 = Utf8               <init>
      #11 = NameAndType        #10:#9         // "<init>":()V
      #12 = Methodref          #2.#11         // scala/HelloWorld$."<init>":()V
      #13 = Utf8               main
      #14 = Utf8               ([Ljava/lang/String;)V
      #15 = Utf8               scala/Predef$
      #16 = Class              #15            // scala/Predef$
      #17 = Utf8               Lscala/Predef$;
      #18 = NameAndType        #6:#17         // MODULE$:Lscala/Predef$;
      #19 = Fieldref           #16.#18        // scala/Predef$.MODULE$:Lscala/Predef$;
      #20 = Utf8               HelloWorld
      #21 = String             #20            // HelloWorld
      #22 = Utf8               println
      #23 = Utf8               (Ljava/lang/Object;)V
      #24 = NameAndType        #22:#23        // println:(Ljava/lang/Object;)V
      #25 = Methodref          #16.#24        // scala/Predef$.println:(Ljava/lang/Object;)V
      #26 = Utf8               this
      #27 = Utf8               args
      #28 = Utf8               [Ljava/lang/String;
      #29 = Methodref          #4.#11         // java/lang/Object."<init>":()V
      #30 = NameAndType        #6:#7          // MODULE$:Lscala/HelloWorld$;
      #31 = Fieldref           #2.#30         // scala/HelloWorld$.MODULE$:Lscala/HelloWorld$;
      #32 = Utf8               Code
      #33 = Utf8               LocalVariableTable
      #34 = Utf8               LineNumberTable
      #35 = Utf8               SourceFile
      #36 = Utf8               ScalaInlineInfo
      #37 = Utf8               Scala
    {
      public static final scala.HelloWorld$ MODULE$;
        descriptor: Lscala/HelloWorld$;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    
      public static {};
        descriptor: ()V
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: new           #2                  // class scala/HelloWorld$
             3: invokespecial #12                 // Method "<init>":()V
             6: return
    
      public void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: getstatic     #19                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
             3: ldc           #21                 // String HelloWorld
             5: invokevirtual #25                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
             8: return
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0       9     0  this   Lscala/HelloWorld$;
                0       9     1  args   [Ljava/lang/String;
          LineNumberTable:
            line 9: 0
    }
    SourceFile: "HelloWorld.scala"
    
    

    从输出结果可以知道:

    HelloWorld$类有一个静态字段

      public static final HelloWorld$ MODULE$;
        flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    

    它的访问修饰符是 public static final , 类型是 HelloWorld, 字段名是 MODULE

    HelloWorld$类还有一个静态初始化方法:

    
    public static {};
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: new           #2                  // class HelloWorld$
             3: invokespecial #12                 // Method "<init>":()V
             6: return
    

    在这个静态初始化方法中, 使用new指令创建了一个 HelloWorld对象, 并且调用该对象的构造方法<init>初始化这个对象。 实际上就是对静态字段MODULE 的赋值。

    HelloWorld$类还有一个main方法:

    public void main(java.lang.String[]);
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=2, args_size=2
             0: getstatic     #19                 // Field scala/Predef$.MODULE$:Lscala/Predef$;
             3: ldc           #21                 // String HelloWorld
             5: invokevirtual #25                 // Method scala/Predef$.println:(Ljava/lang/Object;)V
             8: return
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                   0       9     0  this   LHelloWorld$;
                   0       9     1  args   [Ljava/lang/String;
          LineNumberTable:
            line 5: 0
    

    这个main方法不是静态的, 是一个实例方法, 从它的字节码指令可以看出, 实现的是打印字符串HelloWorld的逻辑。

    HelloWorld的实现方式总结

    从上面的讲述中, 我们可知, scalac编译器使用两个class文件, 实现HelloWorld.scala源文件中的逻辑, 除了生成HelloWorld.class外, 还生产一个HelloWorld$.class 。实现逻辑如下:

    1 传统意义上的入口main方法被编译在HelloWorld.class中
    2 在HelloWorld.class中的main方法中, 会访问HelloWorld.class中的静态字段MODULE (这个字段的类型就是HelloWorld) , 并使用这个字段调用HelloWorld中的main方法。

    HelloWorld中的逻辑有点像下面这样(以下伪代码旨在说明原理, 并不符合java或scala的语法):

    public class HelloWorld{
        
        public static void main(String[] args){
     
     
          HelloWorld$.MODULE$.main(args);
        }   
    }
    

    3 真正打印字符串“HelloWorld”的逻辑在 HelloWorld中。 这个类有一个main实例方法, 来处理打印字符串的逻辑, 并且该类中有一个HelloWorld类型的静态字段MODULE。 上面的HelloWorld类中的入口main方法, 正是通过这个字段调用的HelloWorld的main实例方法来打印"HelloWorld" 。

    HelloWorld$中的代码有点像这样(以下伪代码旨在说明原理, 并不符合java或scala的语法):

    public final class HelloWorld${
     
        public static final HelloWorld$ MODULE$ = new HelloWorld$();
     
        public void main(String[] args){
            println("HelloWorld");
        }
    }
    
    

    相关文章

      网友评论

          本文标题:Scala:HelloWorld

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