标签:
我们知道, Scala也是一种运行于Java虚拟机上的语言, 既然能够运行于虚拟机之上, 那么它必然可以编译成class文件, 因为虚拟机只认class文件。 所以, scalac编译器将.scala源文件, 编译成class文件, 然后这些class文件被虚拟机加载并执行。
所以, 如果你对class文件格式和java虚拟机足够了解的话, 那么学习scala语言就会相对简单。Java文件编译成class文件, 而Scala源文件也是编译成class文件, 虽然他们语法大相径庭, 但是最后殊途同归。 如果我们能基于class文件分析scala的行为, 有助于理解scala语言。有人说scala的语法很多很难, 其实语法终归是写法, class文件的格式是不变的, 可以把scala的语法看成java语法的高级语法糖。
本系列博客基于分析class文件, 来分析scala的语法。 如果你对class文件格式不熟悉, 建议读一下我的专栏, 该专栏是专门分析class文件和JVM行为的。 专栏地址:
http://blog.csdn.net/column/details/zhangjg-java-blog.html
Scala的HelloWorld
按照IT界的传统, 下面我们就从HelloWorld开始分析。 下面是scala版的HelloWorld源码:
-
object HelloWorld{
-
def main(args : Array[String]){
-
println("HelloWorld")
-
}
-
}
如果对scala的语法不是很熟悉, 并且对scala比较感兴趣, 建议先熟悉一下scala的基本语法。 这里简单说两以下几点:
1 以object关键字修饰一个类名, 这种语法叫做孤立对象,这个对象是单例的。 相当于将单例类和单例对象同时定义。
2 方法声明以def开头, 然后是方法名, 参数列表, 返回值, 等号, 方法体 。如下:
-
def doSomeThing(x : Int) : Int = {
-
x += 1
-
}
如果没有返回值, 可以省略等号, 直接写方法体。
3 Array[String]是scala的一种数据类型, 可以理解为字符串数组。
这篇博客的目的不是详细的讲解语法, 而是基于class文件来分析scala语法的实现方式, 所以对于语法只简单提一下 。
反编译scala HelloWorld
我们所说的反编译, 是指使用javap工具反编译class文件, 所以, 在反编译之前, 要先使用scalac编译器编译该源文件:
命令执行完成后, 可以看到HelloWorld.scala所在的目录中多出两个class文件:
其中有一个是和HelloWorld.scala对应的HelloWorld.class 。 那么HelloWorld$.class是什么呢?难道一个scala类可以生成多个class吗? 下面通过反编译来找到答案。
首先反编译HelloWorld.class :
-
javap -c -v -classpath . HelloWorld
反编译结果如下: (为了便于讲述, 给出了所有的输出, 会有些长)
-
Classfile /D:/Workspace/scala/scala-test/HelloWorld/HelloWorld.class
-
Last modified 2014-4-1; size 586 bytes
-
MD5 checksum 2ce2089f345445003ec6b4ef4ed4c6d1
-
Compiled from "HelloWorld.scala"
-
public final class HelloWorld
-
SourceFile: "HelloWorld.scala"
-
RuntimeVisibleAnnotations:
-
0: #6(#7=s#8)
-
ScalaSig: length = 0x3
-
05 00 00
-
minor version: 0
-
major version: 50
-
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
-
Constant pool:
-
#1 = Utf8 HelloWorld
-
#2 = Class #1
-
#3 = Utf8 java/lang/Object
-
#4 = Class #3
-
#5 = Utf8 HelloWorld.scala
-
#6 = Utf8 Lscala/reflect/ScalaSignature;
-
#7 = Utf8 bytes
-
#8 = Utf8 :Q!\t\t!S3mY><vN7ea ...
-
#9 = Utf8 main
-
#10 = Utf8 ([Ljava/lang/String;)V
-
#11 = Utf8 HelloWorld$
-
#12 = Class #11
-
#13 = Utf8 MODULE$
-
#14 = Utf8 LHelloWorld$;
-
#15 = NameAndType #13:#14
-
#16 = Fieldref #12.#15
-
#17 = NameAndType #9:#10
-
#18 = Methodref #12.#17
-
#19 = Utf8 Code
-
#20 = Utf8 SourceFile
-
#21 = Utf8 RuntimeVisibleAnnotations
-
#22 = Utf8 ScalaSig
-
{
-
public static void main(java.lang.String[]);
-
flags: ACC_PUBLIC, ACC_STATIC
-
Code:
-
stack=2, locals=1, args_size=1
-
0: getstatic #16
-
3: aload_0
-
4: invokevirtual #18
-
7: return
-
}
从输出结果可以看到, 这个类确实有传统意义上的main方法。 这个main方法中的字节码指令大概是这样:
1 getstatic访问一个静态字段, 这个静态字段是定义在HelloWorld$类中的MODULE$字段, 这个字段的类型是HelloWorld$ 。 讲到这里, 大概出现了单例的影子。 我们并没有定义这个类, 所以这个类是scala编译器自动生成的, 用来辅佐HelloWorld类。
2 然后使用这个静态对象调用main方法, 这个main方法是HelloWorld$类中的, 而不是当前HelloWorld中的。 它不是静态的, 而是成员方法。
下面反编译HelloWorld$类:
-
javap -c -v -classpath . HelloWorld$
反编译结果如下:
-
Classfile /D:/Workspace/scala/scala-test/HelloWorld/HelloWorld$.class
-
Last modified 2014-4-1; size 596 bytes
-
MD5 checksum 7b3e40952539579da28edc84f370ab9b
-
Compiled from "HelloWorld.scala"
-
public final class HelloWorld$
-
SourceFile: "HelloWorld.scala"
-
Scala: length = 0x0
-
-
minor version: 0
-
major version: 50
-
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
-
Constant pool:
-
#1 = Utf8 HelloWorld$
-
#2 = Class #1
-
#3 = Utf8 java/lang/Object
-
#4 = Class #3
-
#5 = Utf8 HelloWorld.scala
-
#6 = Utf8 MODULE$
-
#7 = Utf8 LHelloWorld$;
-
#8 = Utf8 <clinit>
-
#9 = Utf8 ()V
-
#10 = Utf8 <init>
-
#11 = NameAndType #10:#9
-
#12 = Methodref #2.#11
-
#13 = Utf8 main
-
#14 = Utf8 ([Ljava/lang/String;)V
-
#15 = Utf8 scala/Predef$
-
#16 = Class #15
-
#17 = Utf8 Lscala/Predef$;
-
#18 = NameAndType #6:#17
-
#19 = Fieldref #16.#18
-
#20 = Utf8 HelloWorld
-
#21 = String #20
-
#22 = Utf8 println
-
#23 = Utf8 (Ljava/lang/Object;)V
-
#24 = NameAndType #22:#23
-
#25 = Methodref #16.#24
-
#26 = Utf8 this
-
#27 = Utf8 args
-
#28 = Utf8 [Ljava/lang/String;
-
#29 = Methodref #4.#11
-
#30 = NameAndType #6:#7
-
#31 = Fieldref #2.#30
-
#32 = Utf8 Code
-
#33 = Utf8 LocalVariableTable
-
#34 = Utf8 LineNumberTable
-
#35 = Utf8 SourceFile
-
#36 = Utf8 Scala
-
{
-
public static final HelloWorld$ MODULE$;
-
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
-
-
-
public static {};
-
flags: ACC_PUBLIC, ACC_STATIC
-
Code:
-
stack=1, locals=0, args_size=0
-
0: new #2
-
3: invokespecial #12
-
6: return
-
-
public void main(java.lang.String[]);
-
flags: ACC_PUBLIC
-
Code:
-
stack=2, locals=2, args_size=2
-
0: getstatic #19
-
3: ldc #21
-
5: invokevirtual #25
-
8: return
-
LocalVariableTable:
-
Start Length Slot Name Signature
-
0 9 0 this LHelloWorld$;
-
0 9 1 args [Ljava/lang/String;
-
LineNumberTable:
-
line 5: 0
-
}
从输出结果可以知道:
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
-
3: invokespecial #12
-
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
-
3: ldc #21
-
5: invokevirtual #25
-
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编译的class字节码实现
标签:
原文地址:http://blog.csdn.net/shenxiaoming77/article/details/51491531