Scala简介
它的老大哥Java曾几何时横扫市场,Scala语言相比之下显得名不见经传。直到互联网也开始经历一场“百年未有之大变局”:go, kotlin, python等语言异军突起…….
(图片来自网络)
大数据时代到来,让Scala迎来了春天。Spark框架现在已经成为目前大数据事实上的计算标准框架,而Spark的原生开发语言正是默默无闻的Scala。Spark的开发人员为什么要选择Scala作为开发语言呢?
Scala的名字和设计理念都来源于:Scalable(可扩展的)。它融入了声明式编程,函数式编程的思想,同时将原本的OOP思想发扬光大,大大提高了大数据计算的开发效率。
还有一个背景:Java在大数据开发中仍旧占领着话语权:比如要使用Hadoop生态圈下的各种技术(Kafka,ZooKeeper,其它跟Apache基金会扯上关系的项目,etc),有个最基本的要求:配置JDK。
但Java的缺陷也比较明显:它太庞大了(这个情况在2013年Java 8版本之后有了改观,目前为止JDK已经更新到14版本,已经有了相当多的新内容)。完成一个功能,Python可能只需要1句话,而Java则需要手写大量的逻辑……显然拿Java去编写一个计算框架需要非常非常冗长的代码。
Spark的开发者想要寻找一个这样的开发工具:由它开发的框架既能够兼容其它运行在JVM的大数据框架,又想让代码变得简洁高效。如此看来,最适合的语言非Scala莫属。
Scala也站在“巨人的肩膀”上解决问题:它的老大哥Java留下的车轮,它都可以拿来用。同样的,它也为Java提供了很多新的思路:比如Java 8的诞生。
Scala与Java的关系
1、 除了Scala自身的独特语法之外,还能够支持部分Java的语法。
2、 SDK是依赖于JDK之上运行的。
3、 Scala推崇函数式编程:它引入了偏函数,函数柯里化,高阶函数的概念,函数是Scala的一等公民。
4、 从底层来看,所有的Scala代码实际上是Java类/接口的包装。
5、 Scala 和 Java 8 编译器都出自大神马丁·奥德斯基,因此在Java 8中多多少少能看到一点Scala的“影子”。
6、 Scala将OOP思想发挥了极致!它的“基本”数据类型也属于对象,并像Java的包装类一样提供了很多便捷方法。
7、 Java有两个命令:javac
和java
,分别用于编译和运行。Scala也有对应的两个命令:scalac
和scala
。
Scala语言的特点
不同于Python,javaScript等解释型语言,Scala同Java一样,是一门静态类型编程语言。先编译,然后在JVM上运行。
Scala是一门多范式的编程语言,Scala支持面向对象和函数式编程,且函数的地位被大大提高了。
Scala参考了Java的设计思想,同时又将函数式编程的思想融入到了Java中。
Scala环境安装
Scala的运行依赖于JDK。并且SDK的版本号需要与其相对应,请注意。
windows平台
1、 Windows10下安装scala可以选择.msi一键安装,安装程序会自动按照安装目录配置SCALA_HOME和PATH。但是注意一点!!!安装路径绝对不能带空格和中文,一般程序默认的安装路径是C:/program file,该路径带空格,因此容易出现错误。
2、 也可以选择下载zip包然后直接解压到对应的目录当中,但是需要人工配置SCALA_HOME和PATH,配置方式与JDK无异。
Linux平台
1、 LInux(CENTOS)系统下安装scala,则需要从官网上下载tgz格式的压缩包,发送到虚拟机中并解压,配置。
#--------profile文件新增以下配置-----------#
export SCALA_HOME={安装目录}
export PATH= $PATH:$SCALA_HOME/bin
#------------重启配置--------------——-----#
source /etc/profile
Scala开发工具
实际上,一个大数据项目常常是Java与Scala进行混合开发,因此Scala的开发工具仍然选择Jet Brains公司旗下的IntelliJ IEDA。
但是IDEA没有默认支持Scala开发,需要导入Scala插件Plugin。在IDEA的settings页面下载对应版本。
Jet Brains的插件地址如下,可以下载到本地后选择:install from the disk.
开发Scala代码选择在Windows环境中进行。
从“Hello Scala”开始创建第一个scala程序
感受一下Scala主函数和Java主函数的区别:
object HelloScala {
//定义 主函数(参数:数组[里面是String类型]):unit = {结构体}
def main(args:Array[String]): unit ={
}
}
//Java type:
//public class HelloJava {
// public static void main(String[] args) {
// System.out.println("Hello Java!");
// }
//}
Scala中的object表示它是一个伴生对象(有关于它的具体定义在后续的篇章中介绍),后面的HelloScala即这个伴生对象的名字。
伴生对象的底层实现是HelloScala$。为了观察这个现象,我们在Windows系统下用NotePad来编辑一个scala文件,并用原始的scalac
来编译它,会得到两个.class
文件:
为了查看这两个文件的内容以及差异,我们使用jd-gui.exe对class文件进行反编译:
//----------------------HelloScala.class------------------------//
import scala.reflect.ScalaSignature;
//忽略部分代码
public final class HelloScala
{
public static void main(String[] paramArrayOfString)
{
HelloScala..MODULE$.main(paramArrayOfString);
}
}
//----------------------HelloScala$.class-----------------------//
import scala.Predef$;
public final class HelloScala$
{
public static final MODULE$;
static
{
new ();
}
public void main(String[] args)
{
Predef$.MODULE$.println("Hello Scala!");
}
private HelloScala$()
{
MODULE$ = this;
}
}
观察编译的代码,我们就能发现本质上是HelloScala.class中的主函数实际上是包装了HelloScala$.class中的MODULE$的main实例方法。
如何用jd-gui去查看.class文件代码,笔者在之前已经提到过。
为什么用txt打开.class文件会乱码,而打开.java文件却不会乱码?
为什么要进行一个这样的包装过程呢?
Scala的设计者马丁·奥德斯基将程序的静态内容和非静态内容区分开来,他认为静态的内容不应该属于OOP对象的范畴。
譬如Circle类的static变量:Pi,它实际上跟具体的圆对象无关,对圆这个类来说,Pi是一个”貌合神离”的全局变量。
因此设计者将同一个类的非静态部分声明为class:伴生类,将它的静态部分声明为object:伴生对象。(该部分在后续的学习中继续补充)
HelloScala的主函数声明
def
关键字表示声明一个方法。如果声明该方法是主函数(程序的入口),则方法(函数)名必须声明为main。这是一个规定,就比如Java的主函数必须为(Public static void main())。
Scala的声明变量特点是参数名在前,类型在后。
Array[String]
代表这是一个String类型的Array。Scala的[]括号作用与Java的<>相当:用于指代泛型。
def main(args:....):Unit={...}
中的:Unit =
代表该函数结构体的返回值是void类型。而Unit也是一个真实存在的一个类。
Scala和Java的编译器有相同的地方,又有很多不同。比如可以使用scala命令来运行javac编译过的.class文件,但反之,用java命令来运行scalac编译过的.class文件,则不一定会执行成功。
注意,运行主函数的类需要声明为object,而非class。就像Java的主函数要在静态域中运行一样,Scala的主函数也在静态域运行,而Scala中,由伴生对象来保存一个类的静态部分。
使用Java类来模拟Scala的运行机制
复习Java的两个类修饰符
在此之前,首先复习Java类的两个类修饰符号:public
,或者缺省。一个.java文件可以存在多个class,甚至不包含class(比如:创建了空文件,但是什么都没有写)。
一个.java文件内最多只能有一个声明为public class。且声明为public class的名字要与.java文件名称必须相同,而同一个.java内的其它非public class则没有限制。
public class PublicClazz {
public static void main(String[] args) {}
}
class defaultCLazz{ }
同一个.java文件内声明的多个class可以看作它们在同一个包下,可以相互调用。与该.java文件内的其它class对象也可以调用public属性。但它们对于外部的包则不可见。
在编译时,javac会将同一个.java内的多个class拆分出来,编译出多个.class文件。除了在.java文件中标注为public的class之外,都会被编译成时default类型。
然而从习惯上,都是一个.java文件里只封装一个同名class,这会使得整体的架构更加明确。
使用Java代码来复原Scala底层代码
使用java代码来还原Scala的包装过程,这是一个HelloScala.java文件:
package simulation;
//对外使用的HelloScala类。
public class HelloScala {
public static void main(String[] args) {
HelloScala$.MODULE$.main(args);
}
}
class HelloScala${
static final HelloScala$ MODULE$;
//在静态域中初始化它。
static {
MODULE$ = new HelloScala$();
}
public void main(String[] args){
System.out.println("真正被执行的主函数");
}
}
这段java程序还原了scala.class文件90%的内容。
这个程序真正执行的是MODULE$的main方法。我们可以注意到,MODULE$被static,final修饰。也就是HelloScala伴生对象的实例,它们本质上都维护同一个MODULE$静态实例,符合单例模式的设计。
在Scala中,通过把变量放置在一个单例对象保管的方式,来实现Java的静态功能,这个专门保存单例对象的类在Scala中称之为伴生对象,它使用一个独立的object
关键字来修饰,而非class
。
下图表示Scala的一个伴生对象HelloScala由HelloScala.class
和HelloScala$.class
共同组成。
参考菜鸟教程中的设计模式:
Scala的运行方式
1、 .scala文件经scalac编译成.class文件,再运行。
2、 .scala文件直接由scala运行,但是速度很慢,且不会生成.class文件。(我们也几乎不会这么做)
Scala的注意事项
1、 严格区分大小写。
2、 若一行一条语句,则不需要加分号。若一行多条语句,则中间的语句之间需要加分号。
3、 源文件以.scala为拓展名,但在底层都被编译成了.class文件。
4、 Scala的程序执行入口是main函数,主函数运行在伴生对象object中。
Scala的字符串输出
Scala支持三种方式来输出字符串。除了Java和C风格的输出外,还引用了Linux风格的$引用方式。注意细节:若使用’$’来引用的话,则字符串前面要加上一个s标识。
var string1 : String = "Hello"
var string : String = "Scala"
//与Java的System.out.println无异。
println(string1 + "," + string)
//c语言的格式化输出
printf("%s,%s",string1,string)
//Linux的$引用方式
printf(s"$string,$string1")
如何查看Scala关联的源码?
在刚刚安装完Scala开发插件和SDK时,源码并没有被关联,因此在编程时看不到.scala的底层实现,比较麻烦。在这里先关联源码。
按住Ctrl,单击任何一个Scala类,即可追溯到该类的声明文件,并且IDEA会提示Sources not found。
从官网下载scala-sources-2.xx.x.tar文件夹,解压到SDK的lib目录下,注意,版本要对应。然后再Attach Sources
选项中选择关联即可。
在安装之前可以先用scala -version
指令确认版本。
Scala文档注释
首先在.scala程序内进行doc风格注释:
object ScalaDocTest {
/**
* 此为Scala中的主函数声明。必须要将该主函数声明在伴生对象object中。
* @param args 运行时参数
*/
def main(args: Array[String]): Unit = {
println("This is a main function.")
}
/**
* 该方法是静态的,因为它声明在了伴生对象ScalaDocTest中。
* @return 返回一个"Hello Scala"字符串。
*/
def greet():String ={
return "Hello Scala"
}
}
在IDEA下可以打开Terminal,默认为项目的根目录。进入到scala文件所在的目录,执行以下命令,可以*.scala选中某个目录下的所有.scala文件:
scaladoc -d {目标路径} {scala文件所在路径}
命令行会提示一句:model contains x ducumentable templates.表示找到x处可以生成文档的地方并生成。
随后可以在指定目录里查看到.scala文件内的文档注释被提取为了html格式。