简介
Scala 是 Scalable Language 的简写,是一门多范式的编程语言,一种类似java的编程语言,集成面向对象编程和函数式编程的各种特性。
联邦理工学院洛桑(EPFL)的Martin Odersky于2001年基于Funnel的工作开始设计Scala。Funnel是把函数式编程思想和Petri网相结合的一种编程语言。
Odersky先前的工作是Generic Java和javac(Sun Java编译器)。Java平台的Scala于2003年底/2004年初发布。.NET平台的Scala发布于2004年6月。该语言第二个版本,v2.0,发布于2006年3月。
截至目前,最新版本是版本2.12.2 。
2009年4月,Twitter宣布他们已经把大部分后端程序从Ruby迁移到Scala,其余部分也打算要迁移。此外, Wattzon已经公开宣称,其整个平台都已经是基于Scala基础设施编写的。
特性
面向对象特性
Scala是一种纯面向对象的语言,每个值都是对象。对象的数据类型以及行为由类和特质描述。
类抽象机制的扩展有两种途径:一种途径是子类继承,另一种途径是灵活的混入机制(类似java interface,但有很大的不同)。这两种途径能避免多重继承的种种问题。
函数式编程
Scala也是一种函数式语言,其函数也能当成值来使用。Scala提供了轻量级的语法用以定义匿名函数,支持高阶函数,允许嵌套多层函数,并支持柯里化。Scala的case class及其内置的模式匹配相当于函数式编程语言中常用的代数类型。
更进一步,程序员可以利用Scala的模式匹配,编写类似正则表达式的代码处理XML数据。
静态类型
Scala具备类型系统,通过编译时检查,保证代码的安全性和一致性。类型系统具体支持以下特性:
- 泛型类
- 协变和逆变
- 标注
- 类型参数的上下限约束
- 把类别和抽象类型作为对象成员
- 复合类型
- 引用自己时显式指定类型
- 视图
- 多态方法
扩展性
Scala的设计秉承一项事实,即在实践中,某个领域特定的应用程序开发往往需要特定于该领域的语言扩展。Scala提供了许多独特的语言机制,可以以库的形式轻易无缝添加新的语言结构:
- 任何方法可用作前缀或后缀操作符
- 可以根据预期类型自动构造闭包。
并发性
Scala使用Actor作为其并发模型,Actor是类似线程的实体,通过邮箱发收消息。Actor可以复用线程,因此可以在程序中可以使用数百万个Actor,而线程只能创建数千个。在2.10之后的版本中,使用Akka作为其默认Actor实现。
Scala Web 框架
以下列出了两个目前比较流行的 Scala 的 Web应用框架:
scala安装与配置
jdk安装
不再赘述
scala下载
配置
- 配置SCALA_HOME环境变量
环境变量Path中加入%SCALA_HOME%\bin;
运行scala命令
scala基础语法
Scala 与 Java 的最大区别是:Scala 语句末尾的分号 ; 是可选的。
我们可以认为 Scala 程序是对象的集合,通过调用彼此的方法来实现消息传递。接下来理解下,类,对象,方法,实例变量的概念:
- 对象 - 对象有属性和行为。例如:一只狗的状属性有:颜色,名字,行为有:叫、跑、吃等。对象是一个类的实例。
- 类 - 类是对象的抽象,而对象是类的具体实例。
- 方法 - 方法描述的基本的行为,一个类可以包含多个方法。
- 字段 - 每个对象都有它唯一的实例变量集合,即字段。对象的属性通过给字段赋值来创建。
hello world
依然从hello world开始
1 | object HelloWorld { |
与java对比
再看一个例子
1 | class HelloWorldTest { |
object:
Scala中没有static静态修饰符,在object下的成员全部都是静态的,如果在类中声明了与该类相同的名字的object则该object是该类的“伴生对象”
可以理解把类中的static集中放到了object对象中,伴生对象和类文件必须是同一个源文件,可以用伴生对象做一些初始化的操作。
class:
和java中的class类似。
extends
同java
trait(interface):
在java中可以通过interface实现多重继承,在Scala中可以通过特征(trait)实现多重继承,不过与java不同的是,它可以定义自己的属性和实现方法体,在没有自己的实现方法体时可以认为它时java interface是等价的,在有自己的实现方法时,相当于java中的抽象类。在Scala中也是一般只能继承一个父类,可以通过多个with进行多重继承。
基本语法
- 区分大小写 - Scala是大小写敏感的,这意味着标识Hello 和 hello在Scala中会有不同的含义。
类名 - 对于所有的类名的第一个字母要大写。
如果需要使用几个单词来构成一个类的名称,每个单词的第一个字母要大写。
示例:class MyFirstScalaClass
方法名称 - 所有的方法名称的第一个字母用小写。
如果若干单词被用于构成方法的名称,则每个单词的第一个字母应大写。
示例:def myMethodName()
程序文件名 - 程序文件的名称应该与对象名称完全匹配。
保存文件时,应该保存它使用的对象名称(记住Scala是区分大小写),并追加”.scala”为文件扩展名。 (如果文件名和对象名称不匹配,程序将无法编译)。
示例: 假设”HelloWorld”是对象的名称。那么该文件应保存为’HelloWorld.scala”
- def main(args: Array[String]) - Scala程序从main()方法开始处理,这是每一个Scala程序的强制程序入口部分。
scala关键字
下表列出了 scala 保留关键字,我们不能使用以下关键字作为变量:
abstract | case | catch | class |
def | do | else | extends |
false | final | finally | for |
forSome | if | implicit | import |
lazy | match | new | null |
object | override | package | private |
protected | return | sealed | super |
this | throw | trait | try |
true | type | val | var |
while | with | yield | |
- | : | = | => |
<- | <: | <% | >: |
# | @ | catch | class |
scala 注释(其实跟java一样)
Scala 同 Java 支持单行和多行注释。
换行符
Scala是面向行的语言,语句可以用分号(;)结束或换行符。Scala 程序里,语句末尾的分号通常是可选的。如果你愿意可以输入一个,但若一行里仅 有一个语句也可不写。另一方面,如果一行里写多个语句那么分号是需要的。例如
1 | val s = "hello world!"; println(s) |
引用
Scala 使用 import 关键字引用包。
1 | import java.awt.Color // 引入Color |
import语句可以出现在任何地方,而不是只能在文件顶部。import的效果从开始延伸到语句块的结束。这可以大幅减少名称冲突的可能性。
如果想要引入包中的几个成员,可以使用selector(选取器):
1 | import java.awt.{Color, Font} |
注意:默认情况下,Scala 总会引入 java.lang. 、 scala. 和 Predef._,这里也能解释,为什么以scala开头的包,在使用时都是省去scala.的。
scala 变量
变量声明
在 Scala 中,使用关键词 “var” 声明变量,使用关键词 “val” 声明常量(类似java中的final修饰符)。
1 | var testMsg : String = "testMsg" |
变量testMsg可以修改,不断赋值;而常量testNum不能被更改。
变量类型声明
变量的类型在变量名之后等号之前声明。定义变量的类型的语法格式如下:
1 | var VariableName : DataType [= Initial Value] |
变量声明不一定需要初始值,以下也是正确的:
1 | var testNum :Int |
变量类型引用
在 Scala 中声明变量和常量不一定要指明数据类型,在没有指明数据类型的情况下,其数据类型是通过变量或常量的初始值推断出来的。所以,如果在没有指明数据类型的情况下声明变量或常量必须要给出其初始值,否则将会报错。
1 | var testNum = 10 |
以上实例中,testNum 会被推断为 Int 类型,testMsg 会被推断为 String 类型。
Scala 多个变量声明
Scala 支持多个变量的声明:
1 | val xmax, ymax = 100 // xmax, ymax都声明为100 |
Scala 访问修饰符
Scala 访问修饰符基本和Java的一样,分别有:private,protected,public。
如果没有指定访问修饰符符,默认情况下,Scala对象的访问级别都是 public。
Scala 中的 private 限定符,比 Java 更严格,在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员。
private成员
用private关键字修饰,带有此标记的成员仅在包含了成员定义的类或对象内部可见,同样的规则还适用内部类。
1 | class PrivateAccessTest { |
new Inner().privateMethod()访问不合法是因为 privateMethod方法 在 Inner 类中被声明为 private,而访问不在类Inner之内。但在 InnerMost 里访问privateMethod方法就没有问题的,因为这个访问包含在 Inner 类之内。
Java中允许这两种访问,因为它允许外部类访问内部类的私有成员。
protected成员
在 scala 中,对保护(Protected)成员的访问比 java 更严格一些。因为它只允许保护成员在定义了该成员的的类的子类中被访问。而在java中,用protected关键字修饰的成员,除了定义了该成员的类的子类可以访问,同一个包里的其他类也可以进行访问。
public成员
Scala中,如果没有指定任何的修饰符,则默认为 public。这样的成员在任何地方都可以被访问。
作用域保护
Scala中,访问修饰符可以通过使用限定词强调。格式为:
1 | private[x] |
这里的x指代某个所属的包、类或单例对象。如果写成private[x],读作”这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像可见外,对其它所有类都是private。
scala if else
同java
scala循环
循环类型同java,主要分为while循环,do…while循环和for循环。但for循环在写法上有较大不同。
for循环
1 | for( var x <- Range ){ |
Range 可以是一个数字区间表示 i to j ,或者 i until j。左箭头 <- 用于为变量 x 赋值。
i to j
表示 i<= x <= j
1 | for (a <- 1 to 5) { |
i until j
表示 i<= x < j
1 | for (a <- 1 until 5) { |
for集合循环
1 | val numList = List(1,2,3,4,5); |
for 循环过滤
Scala 可以使用一个或多个 if 语句来过滤一些元素。通用表达式如下。
1 | for( var x <- List |
例子如下
1 | val numList = List(1,2,3,4,5); |
for 使用 yield
将 for 循环的返回值作为一个变量存储。语法格式如下:
1 | var retVal = for{ var x <- List |
scala函数
函数声明
Scala 函数声明格式如下:
1 | def functionName ([参数列表]) : [return type] |
如果不写等于号和方法主体,那么方法会被隐式声明为”抽象(abstract)”。
函数定义
方法定义由一个def 关键字开始,紧接着是可选的参数列表,一个冒号”:” 和方法的返回类型,一个等于号”=”,最后是方法的主体。
Scala 函数定义格式如下:
1 | def functionName ([参数列表]) : [return type] = { |
如果函数没有返回值,可以返回为 Unit,这个类似于 Java 的 void
scala函数-函数传名调用
Scala的解释器在解析函数参数(function arguments)时有两种方式:
- 传值调用(call-by-value):先计算参数表达式的值,再应用到函数内部;
- 传名调用(call-by-name):将未计算的参数表达式直接应用到函数内部
在进入函数内部前,传值调用方式就已经将参数表达式的值计算完毕,而传名调用是在函数内部进行参数表达式的值计算的。
例子如下:
1 | object CallByNameFunction { |
scala函数-指定函数参数名
一般情况下函数调用参数,就按照函数定义时的参数顺序一个个传递。但是也可以通过指定函数参数名,并且不需要按照顺序向函数传递参数,实例如下:
1 | object SpecifyParamFunction { |
scala函数-可变参数
Scala 允许你指明函数的最后一个参数可以是重复的,即我们不需要指定函数参数的个数,可以向函数传入可变长度参数列表。
Scala 通过在参数的类型之后放一个星号来设置可变参数(可重复的参数)。例如:
1 | object VariableParamFunction { |
scala函数-默认参数值
Scala 可以为函数参数指定默认参数值,使用了默认参数,你在调用函数的过程中可以不需要传递参数,这时函数就会调用它的默认参数值,如果传递了参数,则传递值会取代默认值。实例如下:
1 | object DefaultParamFunction { |
scala函数-高阶函数
高阶函数(Higher-Order Function)就是操作其他函数的函数。
Scala 中允许使用高阶函数, 高阶函数可以使用其他函数作为参数,或者使用函数作为输出结果。
以下实例中,invoke() 函数使用了另外一个函数 f 和 值 v 作为参数,而函数 f 又调用了参数 v:
1 | object HigherOrderFunction { |
scala函数-函数嵌套
我们可以在 Scala 函数内定义函数,定义在函数内的函数称之为局部函数。
以下实例我们实现阶乘运算,并使用内嵌函数:
1 | object NestingFunction { |
scala函数-匿名函数
Scala 中定义匿名函数的语法很简单,箭头左边是参数列表,右边是函数体。
下面的表达式就定义了一个接受一个Int类型输入参数的匿名函数:
1 | var inc = (x:Int) => x+1 |
以上实例的 inc 现在可作为一个函数,使用方式如下
1 | var x = inc(7)-1 |
同样可以在匿名函数中定义多个参数:
1 | var mul = (x: Int, y: Int) => x*y |
mul 现在可作为一个函数,使用方式如下:
1 | println(mul(3, 4)) |
也可以不给匿名函数设置参数,如下所示:
1 | var testMsg = () => { println("test msg.") } |
使用方式如下:
1 | println( testMsg() ) |
scala函数-偏应用函数
Scala 偏应用函数是一种表达式,你不需要提供函数需要的所有参数,只需要提供部分,或不提供所需参数。
比如如下例子:
1 | object Test { |
实例中,log() 方法接收两个参数:date 和 message。我们在程序执行时调用了三次,参数 date 值都相同,message 不同。
scala中可以使用偏应用函数优化以上方法,绑定第一个 date 参数,第二个参数使用下划线(_)替换缺失的参数列表,并把这个新的函数值的索引的赋给变量。以上实例修改如下:
1 | object PartialFunction { |
scala函数-函数柯里化(Currying)
柯里化(Currying)指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程。新的函数返回一个以原有第二个参数为参数的函数。
实例如下:
1 | def add(x:Int,y:Int) = x+y |
那么我们应用的时候,应该是这样用:add(1,2)。 现在我们把这个函数变一下形:
1 | def add(x:Int)(y:Int) = x + y |
那么我们应用的时候,应该是这样用:add(1)(2),最后结果都一样是3,这种方式(过程)就叫柯里化。
实现过程如下:
add(1)(2) 实际上是依次调用两个普通函数(非柯里化函数),第一次调用使用一个参数 x,返回一个函数类型的值,第二次使用参数y调用这个函数类型的值。
实质上最先演变成这样一个方法:
1 | def add(x:Int)=(y:Int)=>x+y |
这个函数是一个匿名函数,接收一个x为参数,返回一个匿名函数。而返回的匿名函数的定义是:接收一个Int型参数y,函数体为x+y。现在对这个方法进行调用。
1 | val result = add(1) |
返回一个result,那result的值应该是一个匿名函数:(y:Int)=>1+y
继续调用result,得到结果为3。
1 | val sum = result(2) |
Currying 的重要意义在于可以把函数完全变成「接受一个参数;返回一个值」的固定形式,这样对于讨论和优化会更加方便。
scala闭包
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
闭包通常来讲可以简单的认为是可以访问一个函数里面局部变量的另外一个函数。
1 | var factor = 3 |
在 multi函数 中有两个变量:i 和 factor。其中的一个 i 是函数的形式参数,在 multi 函数被调用时,i 被赋予一个新的值。然而,factor不是形式参数,而是成员变量,而且这个变量定义在函数外面。
这样定义的函数变量 multi 成为一个”闭包”,因为它引用到函数外面定义的变量,定义这个函数的过程是将这个自由变量捕获而构成一个封闭的函数。
scala数组
基本同Java
1 | val z: Array[String] = new Array[String](3) |
区别是索引通过(),而java中通过[]
scala集合
Scala提供了一套很好的集合实现,提供了一些集合类型的抽象。
包括List, Set, Map,等等。
同时兼容JAVA数据结构
scala trait
Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。
与接口不同的是,它还可以定义属性和方法的实现。
一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。
Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait,如下所示:
1 | trait TraitTest { |
以上Trait(特征)由两个方法组成:isEqual 和 isNotEqual。isEqual 方法没有定义方法的实现,isNotEqual定义了方法的实现。子类继承特征可以实现未被实现的方法。所以其实 Scala Trait(特征)更像 Java 的抽象类。
scala类和对象
scala类
首先看一个例子:
1 | class Point(xc: Int, yc: Int) { |
与Java中不同的是
- scala类名称后直接加上构造函数的参数列表,这个构造函数就是主构造函数
- Scala中有只有一个主要构造函数,其他都是辅助构造函数
- 主构造函数和类的定义混杂在一起的,即定义类的时候,就指明了这个类的成员变量参数
- 辅助构造函数必须调用主构造函数或者通过this(…)之间相互调用。
scala对象
Scala比Java更面向对象的一个方面是Scala没有静态成员。替代品是,Scala有单例对象:singleton object。
当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。类和它的伴生对象可以互相访问其私有成员。
类和单例对象间的一个差别是,单例对象不带参数,而类可以。
apply方法
Scala 的apply 有2 张形式,一种是 伴生对象的apply ,一种是 伴生类中的apply
1 | object Point { |
val pt2 = Point(30, 30),调用的是Object下的apply()方法,由于此时并没有创建类的对象,所以也只能调用Object下的静态方法。
val pt = new Point(10, 20), 创建了一个类的对象,并且返回给了变量pt。
object apply() 是一种比较普遍用法,主要用来解决复杂对象的初始化问题。
scala模式匹配
Scala 提供了强大的模式匹配机制,应用也非常广泛。
一个模式匹配包含了一系列备选项,每个都开始于关键字 case。每个备选项都包含了一个模式及一到多个表达式。箭头符号 => 隔开了模式和表达式。
实例如下:
1 | object CaseTest { |
scala异常处理
Scala 的异常处理和Java 类似。
实例如下:
1 | object ExceptionTest { |
只是scala的Exception捕获采用模式匹配方法。
scala文件I/O
Scala 进行文件写操作,直接用的都是 java中 的 I/O 类 (java.io.File):i
写文件实例:
1 | object WriteTest { |
从控制台读取用户输入:
1 | object ConsoleReadLineTest { |