Scala入门:从HelloWorld开始【源码及编译】
最近在学习Scala语言,虽然还没有完全学通, 但是隐约可以体会到Scala的简洁和强大。 它既能让程序员使用函数式编程, 也提供了全面的面向对象编程。 在刚刚开始读《Scala编程》的时候, 刚读了几页, 我就被Scala语言吸引住了, 所以就一直读下去。 在学习的过程中, 也会有一些感悟, 对于一些原理, 也会尽量搞明白。 所以打算一边学习, 一边写博客, 虽然目前还没有深入, 但是还是有很多东西值得写下来。
我们知道, Scala也是一种运行于Java虚拟机上的语言, 既然能够运行于虚拟机之上, 那么它必然可以编译成class文件, 因为虚拟机只认class文件。 所以, scalac编译器将.scala源文件, 编译成class文件, 然后这些class文件被虚拟机加载并执行。
所以, 如果你对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")
}
}
- 如果对scala的语法不是很熟悉, 并且对scala比较感兴趣, 建议先熟悉一下scala的基本语法。 这里简单说两以下几点:
以object关键字修饰一个类名, 这种语法叫做孤立对象,这个对象是单例的。 相当于将单例类和单例对象同时定义。 - 方法声明以def开头, 然后是方法名, 参数列表, 返回值, 等号, 方法体 。如下:
def doSomeThing(x : Int) : Int = {
x += 1
}
- 如果没有返回值, 可以省略等号, 直接写方法体。
Array[String]是scala的一种数据类型, 可以理解为字符串数组。
这篇博客的目的不是详细的讲解语法, 而是基于class文件来分析scala语法的实现方式, 所以对于语法只简单提一下 。
反编译scala HelloWorld
我们所说的反编译, 是指使用javap工具反编译class文件, 所以, 在反编译之前, 要先使用scalac编译器编译该源文件:
scalac HelloWorld.scala
命令执行完成后, 可以看到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 // HelloWorld
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#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 // HelloWorld$
#13 = Utf8 MODULE$
#14 = Utf8 LHelloWorld$;
#15 = NameAndType #13:#14 // MODULE$:LHelloWorld$;
#16 = Fieldref #12.#15 // HelloWorld$.MODULE$:LHelloWorld$;
#17 = NameAndType #9:#10 // main:([Ljava/lang/String;)V
#18 = Methodref #12.#17 // HelloWorld$.main:([Ljava/lang/String;)V
#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 // Field HelloWorld$.MODULE$:LHelloWorld$;
3: aload_0
4: invokevirtual #18 // Method HelloWorld$.main:([Ljava/lang/String;)V
7: return
}
从输出结果可以看到, 这个类确实有传统意义上的main方法。
- 这个main方法中的字节码指令大概是这样:
getstatic访问一个静态字段, 这个静态字段是定义在HelloWorld$类中的MODULE$字段, 这个字段的类型是HelloWorld$ 。 讲到这里, 大概出现了单例的影子。 我们并没有定义这个类, 所以这个类是scala编译器自动生成的, 用来辅佐HelloWorld类。 - 然后使用这个静态对象调用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 // HelloWorld$
#3 = Utf8 java/lang/Object
#4 = Class #3 // java/lang/Object
#5 = Utf8 HelloWorld.scala
#6 = Utf8 MODULE$
#7 = Utf8 LHelloWorld$;
#8 = Utf8 <clinit>
#9 = Utf8 ()V
#10 = Utf8 <init>
#11 = NameAndType #10:#9 // "<init>":()V
#12 = Methodref #2.#11 // 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$:LHelloWorld$;
#31 = Fieldref #2.#30 // HelloWorld$.MODULE$:LHelloWorld$;
#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 // class HelloWorld$
3: invokespecial #12 // Method "<init>":()V
6: return 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
}
从输出结果可以知道:
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 。实现逻辑如下: - 传统意义上的入口main方法被编译在HelloWorld.class中
在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");
}
}
转自:http://blog.csdn.net/zhangjg_blog
Scala入门:从HelloWorld开始【源码及编译】的更多相关文章
- 使用 IntelliJ IDEA 导入 Spark 最新源码及编译 Spark 源代码
前言 其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. 准备工作 1.sca ...
- 使用 IntelliJ IDEA 导入 Spark 最新源码及编译 Spark 源代码(博主强烈推荐)
前言 其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. 准备工作 1.sca ...
- net-snmp源码VS2013编译添加加密支持(OpenSSL)
net-snmp源码VS2013编译添加加密支持(OpenSSL) snmp v3 协议使用了基于用户的安全模型,具有认证和加密两个模块. 认证使用的算法是一般的消息摘要算法,例如MD5/SHA等.这 ...
- net-snmp源码VS2013编译添加加密支持(OpenSSL)(在VS里配置编译OpenSSL)
net-snmp源码VS2013编译添加加密支持(OpenSSL) snmp v3 协议使用了基于用户的安全模型,具有认证和加密两个模块. 认证使用的算法是一般的消息摘要算法,例如MD5/SHA等.这 ...
- Hadoop2.x源码-编译剖析
1.概述 最近,有小伙伴涉及到源码编译.然而,在编译期间也是遇到各种坑,在求助于搜索引擎,技术博客,也是难以解决自身所遇到的问题.笔者在被询问多次的情况下,今天打算为大家来写一篇文章来剖析下编译的细节 ...
- 【转】Android用NDK和整套源码下编译JNI的不同
原文网址:http://www.devdiv.com/android_ndk_jni_-blog-99-2101.html 前些天要写个jni程序,因为才几行代码,想着用ndk开发可能容易些,就先研究 ...
- 【Android 系统开发】CyanogenMod 13.0 源码下载 编译 ROM 制作 ( 手机平台 : 小米4 | 编译平台 : Ubuntu 14.04 LTS 虚拟机)
分类: Android 系统开发(5) 作者同类文章X 版权声明:本文为博主原创文章 ...
- 从源码(编译)安装golang 二
h1 { margin-top: 0.6cm; margin-bottom: 0.58cm; direction: ltr; color: #000000; line-height: 200%; te ...
- MySQL源码包编译安装
+++++++++++++++++++++++++++++++++++++++++++标题:MySQL数据库实力部署时间:2019年3月9日内容:MySQL源码包进行编译,然后部署MySQL单实例重点 ...
- 通过清华大学镜像下载Android源码并编译源码
之前看源码都是在Windows下用SourceInsight看,虽然达到了研究源码的效果,但终究还是有遗憾...趁着周末,准备在Ubuntu虚拟机上下载编译源码. 之前下源码时,有了解一些Androi ...
随机推荐
- linux网络配置完全解析
概述:熟悉了windows下面的网络配置,对linux下的网络配置缺未必了解透彻.熟练掌握linux下的网络配置原理,能帮助我们更容易掌握网络传输原理:同时具备一些网络连接不通对应问题的排查能力.文本 ...
- imperva系统升级遇见的错误(配置文件的导入导出)
今天心态有点炸了 今天去东兴证券做waf升级.浪费了两天才弄完.把客户都弄得有点急了.好歹原厂的工程师耐心的讲解这才弄完.感谢路哥.... 赶紧总结一下. 事情是这样的.东兴 证券的imperva是v ...
- 树莓派开发系列教程3--ssh、vnc远程访问
注意:树莓派系列的3篇文章里面的图片因为博客转移过程丢失了,非常抱歉 前言 远程访问有很多种方式可以实现.比如ssh.telnet.ftp.samba.远程桌面等等,各有优缺点.本文主要以ssh和远程 ...
- C#调用mciSendString播放音频文件
mciSendString函数是一个WinAPI,主要用来向MCI(Media Control Interface)设备发送字符串命令. 一.函数的声明如下: private static exter ...
- 大型网站的 HTTPS 实践(二)——HTTPS 对性能的影响(转)
原文链接:http://op.baidu.com/2015/04/https-s01a02/ 1 前言 HTTPS 在保护用户隐私,防止流量劫持方面发挥着非常关键的作用,但与此同时,HTTPS 也会降 ...
- chkconfig命令主要用来更新(启动或停止)和查询系统服务的运行级信息
chkconfig命令主要用来更新(启动或停止)和查询系统服务的运行级信息.谨记chkconfig不是立即自动禁止或激活一个服务,它只是简单的改变了符号连接. 使用语法:chkconfig [--ad ...
- 交通运输线(LCA)
题目大意: 战后有很多城市被严重破坏,我们需要重建城市.然而,有些建设材料只能在某些地方产生.因此,我们必须通过城市交通,来运送这些材料的城市.由于大部分道路已经在战争期间完全遭到破坏,可能有两个城市 ...
- span文字里面自动换行时怎么办
可以用white-space:nowrap来强制文字不换行,知道遇到<br>为止
- linux下redis的安装与部署
一.Redis介绍 Redis是当前比较热门的NOSQL系统之一,它是一个key-value存储系统.和Memcache类似,但很大程度补偿了Memcache的不足,它支持存储的value类型相对更多 ...
- hdu 5468(dfs序+容斥原理)
Puzzled Elena Time Limit: 5000/2500 MS (Java/Others) Memory Limit: 131072/131072 K (Java/Others)T ...