Scala总结java
===概述mysql
scala是一门以Java虚拟机(JVM)为目标运行环境并将面向对象和函数式编程的最佳特性结合在一块儿的静态类型编程语言。react
scala是纯粹的面向对象的语言。java虽然是面向对象的语言,可是它不是纯粹的,由于java的基本数据类型不是类,而且在java中还有静态成员变量和静态方法。相反,scala是纯粹面向对象的,每一个值都是对象,每一个操做都是方法调用。linux
scala也是一个成熟的函数式语言。函数式编程有两个指导思想:①函数是头等值,也就是说函数也是值,而且和其余类型(如整数、字符串等)处于同一地位,函数能够被看成参数传递,也能够被看成返回值返回,还能够在函数中定义函数等等;②程序的操做应该把输入值映射为输出值而不是就地修改,也就是说函数调用不该产生反作用,虽然函数式编程语言鼓励使用“无反作用”的方法,可是scala并不强制你必须这么作。scala容许你使用指令式的编程风格,可是随着你对scala的深刻了解,你可能会更倾向于一种更为函数式的编程风格。向函数式编程转变,你就应该尽可能去使用val、不可变对象、无反作用方法,而不是var、可变对象、有反作用方法。要明白的是,从指令式编程向函数式编程的转变会很困难,所以你要作好充分的准备,并不断的努力。c++
scala运行于JVM之上,而且它能够访问任何的java类库而且与java框架进行互操做,scala也大量重用了java类型和类库。程序员
第一个scala程序:sql
object ScalaTest {docker
def main(args: Array[String]) {shell
println("hello scala.")数据库
}
}
===scala解释器
安装好scala并配置好PATH环境变量以后,就能够在终端中输入“scala”命令打开scala解释器。在其中,你能够像使用shell同样,使用TAB补全、Ctrl+r搜索、上下方向键切换历史命令等等。退出scala解释器,可使用命令:“:q”或者“:quit”。
因为解释器是输入一句执行一句,所以也常称为REPL。REPL一次只能看到一行代码,所以若是你要在其中粘贴代码段的话,可能会出现问题,这时你可使用粘贴模式,键入以下语句:
:paste
而后把代码粘贴进去,再按下Ctrl+d,这样REPL就会把代码段看成一个总体来分析。
===scala做为脚本运行
scala代码也能够做为脚本运行,只要你设置好代码文件的shell前导词(preamble),并将代码文件设置为可执行。以下:
#!/usr/bin/env scala
println("这是scala脚本")
设置代码文件为可执行,便可执行之啦。
scala脚本的命令行参数保存在名为args的数组中,你可使用args获取命令行输入的程序参数。
===scala编译运行
scala编译器scalac会将scala代码编译为jvm能够运行的字节码,而后就能够在jvm上执行了。假设有一个Hello.scala 文件,咱们就可使用 scalac Hello.scala 编译,而后使用 scala Hello 运行。固然也可使用java工具来运行,但须要在classpath里指定scala-library.jar。对于classpath,在Unix家族的系统上,类路径的各个项目由冒号“:”分隔,在MS Windows系统上,它们由分号“;”分隔。例如,在Linux上你能够输入这样的命令来运行(注意classpath最后加一个“.”):
java -classpath /usr/local/scala-2.10.4/lib/scala-library.jar:. Hello
===scala IDE开发环境
你可使用 eclipse 或者 intellij idea 做为scala的IDE开发环境,但都须要安装scala插件才行。下面分别介绍这两种方式:
eclipse开发环境配置:
scala ide for eclipse(下载地址:http://scala-ide.org)中集成了scala插件,你能够直接使用它进行开发,不过它包含的可能不是咱们想要的scala版本,所以,仍是在该网站上下载对应的scala插件,插在eclipse上,这样更好啊。
咱们先安装eclipse juno,而后下载eclipse juno以及scala 2.10.4对应的scala sdk插件升级包:update-site.zip。将插件解压缩,将 features 和 plugins 目录下的全部东东都复制到eclipse中的对应目录中,重启eclipse便可。而后就能够新建scala project了。
intellij idea开发环境配置:
咱们先安装好intellij idea,而后安装scala插件,自动安装插件有时会很是慢,尤为是在china。咱们仍是手动配置插件吧。请注意插件的版本,必须与当前idea版本兼容。手动配置插件方法以下:
(1) 进入 setting > plugins > browse repositorits 搜索你要下载的插件名称,右侧能够找到下载地址。
(2) 解压插件压缩包,把插件的所有文件都复制到IntelliJ IDEA安装程序的plugins文件夹中,注意插件最好以一个单独的文件夹放在plugins目录下。
(3) 通常重启intellij idea就会自动加载插件,进入 setting > plugins 看看有木有。若是不自动加载的话,进入setting > plugins > install plugin from disk,找到刚才复制的插件位置,再而后就行了。
接下来就能够新建scala project,新建时我选择的是“Scala”(不是sbt,由于我这选择sbt以后,等半天sbt都不会配置好,郁闷啊)。
相关姿式:
什么是SBT? SBT = (not so) Simple Build Tool,是scala的构建工具,与java的maven地位相同。其设计宗旨是让简单的项目能够简单的配置,而复杂的项目能够复杂的配置。
===scala特色
在scala中,语句以后的“;”是可选的,这根据你的喜爱。当有多个语句在同一行时,必须加上分号,但不建议把多个语句放在一行。
在scala中,建议使用2个空格做为代码缩进,不过我咋喜欢一个tab呢 ⊙﹏⊙!
在scala中,符号“_”至关于java中的通配符“*”。
scala相似于c++、java,索引也l是从0开始,但元组是个例外,它从1开始。
===数据类型
scala有7种数值类型:Byte、Char、Short、Int、Long、Float和Double,以及2种非数值类型:Boolean和Unit(只有一个值“()”,至关于java和c++中的void,即空值)。这些类型都是抽象的final类(不能使用new新建,也不能被继承),在scala包中定义,是对java基本数据类型的包装,所以与java基本数据类型有相同的长度。同时,scala还提供了RichInt、RichChar等等,它们分别提供Int、Char等所不具有的便捷方法。
另外,scala沿用了java.lang包中的String。在scala中,常量也称做字面量,字符串字面量由双引号包含的字符组成,同时scala提供了另外一种定义字符串常量的语法——原始字符串,它以三个双引号做为开始和结束,字符串内部能够包含不管何种任意字符。
在scala中,咱们使用方法,而不是强制类型转换,来作数值类型之间的转换,如99.44.toInt、97.toChar。另外也能够参见显式类型转换和隐式转换。
===变量
scala有两种变量:val和var。val如同java中的final变量,var如同java中的非final变量。因为scala是彻底面向对象的,所以val和var只是声明了对象的引用是不可变的仍是可变的,并不能说明引用指向的对象的可变性。声明变量的同时须要初始化之,不然该变量就是抽象的。若是不指定变量的类型,编译器会从初始化它的表达式中推断出其类型。固然你也能够在必要的时候指定其类型,但注意,在scala中变量或函数的类型老是写在变量或函数的名称的后边。示例以下:
val answer = “yes”
val answer, message: String = “yes”
===标识符
scala标识符有四种形式:字母数字标识符、操做符标识符、混合标识符、字面量标识符。
字母数字标识符:跟其余语言相似,由字母、数字和下划线组成,但需注意“$”字符被保留做为scala编译器产生的标识符之用,你不要随意使用它啊。
操做符标识符:由一个或多个操做符字符组成。scala编译器将在内部“粉碎”操做符标识符以转换成合法的内嵌“$”的java标识符。若你想从java代码中访问这个标识符,就应该使用这种内部表示方式。
混合标识符:由字母数字以及后面跟着的下划线和一个操做符标识符组成。如unary_+定义了一个前缀操做符“+”。
字面量标识符:是用反引号`…`包含的任意字符串,scala将把被包含的字符串做为标识符,即便被包含字符串是scala的关键字。例如:你可使用Thread.`yield`()来访问java中的方法,即便yield是scala的关键字。
===操做符
scala的操做符和你在java和C++中的预期效果是同样的,但注意scala并不提供++、--操做符。不过,scala中的操做符实际上都是方法,任何方法均可以看成操做符使用,如 a + b 至关于 a.+(b)。
须要注意的是:对于不可变对象(注:对象的不可变并非说它的引用变量是val的),并不真正支持相似于“+=”这样以“=”结尾的操做符(即方法),不过scala仍是提供了一些语法糖,用以解释以“=”结尾的操做符用于不可变对象的状况。假设a是不可变对象的引用,那么在scala中a += b将被解释为a = a + b,这时就至关于新建一个不可变对象从新赋值给引用a,前提是引用变量a要声明为var的,由于val变量定义以后是不可变的。
更多信息参见函数(方法)部分。
===块表达式与赋值
在scala中,{}块包含一系列表达式,其结果也是一个表达式,块中最后一个表达式的值就是其值。
在scala中,赋值语句自己的值是Unit类型的。所以以下语句的值为“()”:
{r = r * n; n -= 1}
正是因为上述缘由,scala中不能多重赋值,而java和c++却能够多重赋值。所以,在scala中,以下语句中的x值为“()”:
x = y = 1
===控制结构
scala和其余编程语言有一个根本性差别:在scala中,几乎全部构造出来的语法结构都有值。这个特性使得程序结构更加精简。scala内建的控制结构不多,仅有if、while、for、try、match和函数调用等而已。如此之少的理由是,scala从语法层面上支持函数字面量。
if表达式:
scala的if/else语法结构与java等同样,可是在scala中if/else表达式有值,这个值就是跟在if/esle后边的表达式的值。以下:
val s = if(x > 0) 1 else -1
同时注意:scala的每一个表达式都有一个类型,好比上述if/esle表达式的类型是Int。若是是混合类型表达式,则表达式的类型是两个分支类型的公共超类型。String和Int的超类型就是Any。若是一个if语句没有else部分,则当if条件不知足时,表达式结果为Unit。如:
if(x > 0) 1
就至关于:
if(x > 0) 1 else ()
while循环:
scala拥有与java和c++中同样的while和do-while循环,while、do-while结果类型是Unit。
for表达式:
scala中没有相似于for(; ; )的for循环,你可使用以下形式的for循环语句:
for(i <- 表达式)
该for表达式语法对于数组和全部集合类均有效。具体介绍以下:
枚举:for(i <- 1 to 10),其中“i <- 表达式”语法称之为发生器,该语句是让变量i(注意此处循环变量i是val的(但无需你指定),该变量的类型是集合的元素类型)遍历表达式中的全部值。1 to 10产生的Range包含上边界,若是不想包含上边界,可使用until。
过滤:也叫守卫,在for表达式的发生器中使用过滤器能够经过添加if子句实现,如:for(i <- 1 to 10 if i!=5),若是要添加多个过滤器,即多个if子句的话,要用分号隔开,如:for(i <- 1 to 10 if i!=5; if i!=6)。
嵌套枚举:若是使用多个“<-”子句,你就获得了嵌套的“循环”,如:for(i <- 1 to 5; j <- 1 to i)。
流间变量绑定:你能够在for发生器以及过滤器等中使用变量保存计算结果,以便在循环体中使用,从而避免屡次计算以获得该结果。流间变量绑定和普通变量定义类似,它被看成val,可是无需声明val关键字。
制造新集合:for(…) yield 变量/循环体,最终将产生一个集合对象,集合对象的类型与它第一个发生器的类型是兼容的。
实际上:for表达式具备等价于组合应用map、flatMap、filter和foreach这几种高阶函数的表达能力。实际上,全部的可以yield(产生)结果的for表达式都会被编译器转译为高阶方法map、flatMap及filter的组合调用;全部的不带yield的for循环都会被转译为仅对高阶函数filter和foreach的调用。正是因为这几个高阶函数支持了for表达式,因此若是一个数据类型要支持for表达式,它就要定义这几个高阶函数。有些时候,你可使用for表达式代替map、flatMap、filter和foreach的显式组合应用,或许这样会更清晰明了呢。
scala中没有break和continue语句。若是须要相似的功能时,咱们能够:
1) 使用Boolean类型的控制变量
2) 使用嵌套函数,你能够从函数当中return
3) ...
match表达式与模式匹配:
scala中没有switch,但有更强大的match。它们的主要区别在于:
① 任何类型的常量/变量,均可以做为比较用的样本;
② 在每一个case语句最后,不须要break,break是隐含的;
③ 更重要的是match表达式也有值;
④ 若是没有匹配的模式,则MatchError异常会被抛出。
match表达式的形式为:选择器 match { 备选项 }。一个模式匹配包含了一系列备选项,每一个都开始于关键字case。每一个备选项都包含了一个模式以及一到多个表达式,它们将在模式匹配过程当中被计算。箭头符号“=>”隔开了模式和表达式。按照代码前后顺序,一旦一个模式被匹配,则执行“=>”后边的表达式((这些)表达式的值就做为match表达式的值),后续case语句再也不执行。示例以下:
a match {
case 1 => "match 1"
case _ => "match _"
}
match模式的种类以下:
① 通配模式:能够匹配任意对象,通常做为默认状况,放在备选项最后,如:
case _ =>
② 变量模式:相似于通配符,能够匹配任意对象,不一样的是匹配的对象会被绑定在变量上,以后就可使用这个变量操做对象。所谓变量就是在模式中临时生成的变量,不是外部变量,外部变量在模式匹配时被看成常量使用,见常量模式。注意:同一个模式变量只能在模式中出现一次。
③ 常量模式:仅匹配自身,任何字面量均可以做为常量,外部变量在模式匹配时也被看成常量使用,如:
case "false" => "false"
case true => "truth"
case Nil => "empty list"
对于一个符号名,是变量仍是常量呢?scala使用了一个简单的文字规则对此加以区分:用小写字母开始的简单名被看成是模式变量,全部其余的引用被认为是常量。若是常量是小写命名的外部变量,那么它就得特殊处理一下了:若是它是对象的字段,则能够加上“this.”或“obj.”前缀;或者更通用的是使用字面量标识符解决问题,也即用反引号“`”包围之。
④ 抽取器模式:抽取器机制基于能够从对象中抽取值的unapply或unapplySeq方法,其中,unapply用于抽取固定数量的东东,unapplySeq用于抽取可变数量的东东,它们都被称为抽取方法,抽取器正是经过隐式调用抽取方法抽取出对应东东的。抽取器中也能够包含可选的apply方法,它也被称做注入方法,注入方法使你的对象能够看成构造器来用,而抽取方法使你的对象能够看成模式来用,对象自己被称做抽取器,与是否具备apply方法无关。样本类会自动生成伴生对象并添加必定的句法以做为抽取器,实际上,你也能够本身定义一个任意其余名字的单例对象做为抽取器使用,以这样的方式定义的抽取器对象与样本类类型是无关联的。你能够对数组、列表、元组进行模式匹配,这正是基于抽取器模式的。
⑤ 类型模式:你能够把类型模式看成类型测试和类型转换的简易替代,示例以下:
case s: String => s.length
⑥ 变量绑定:除了独立的变量模式以外,你还能够把任何其余模式绑定到变量。只要简单地写上变量名、一个@符号,以及这个模式。
模式守卫:模式守卫接在模式以后,开始于if,至关于一个判断语句。守卫能够是任意的引用模式中变量的布尔表达式。若是存在模式守卫,只有在守卫返回true的时候匹配才算成功。
Option类型:scala为可选值定义了一个名为Option的标准类型,一个Option实例的值要么是Some类型的实例,要么是None对象。分离可选值最一般的办法是经过模式匹配,以下:
case Some(s) => s
case None => “?”
模式无处不在:在scala中,模式能够出如今不少地方,而不仅仅在match表达式里。好比:
① 模式使用在变量定义中,以下:
val myTuple = (123, “abc”)
val (number, string) = myTuple
② 模式匹配花括号中的样本序列(即备选项)能够用在可以出现函数字面量的任何地方,实质上,样本序列就是更广泛的函数字面量,函数字面量只有一个入口点和参数列表,样本序列能够有多个入口点,每一个都有本身的参数列表,每一个样本都是函数的一个入口点,参数被模式所特化。以下:
val withDefault: Option[Int] => String = {
case Some(x) => "is int"
case None => "?"
}
③ for表达式里也可使用模式。示例以下:
for((number, string) <- myTuple) println(number + string)
模式匹配中的中缀标注:带有两个参数的方法能够做为中缀操做符使用,使用中缀操做符时其实是其中一个操做数在调用操做符对应的方法,而另外一个操做数做为方法的参数。但对于模式来讲规则有些不一样:若是被看成模式,那么相似于p op q这样的中缀标注等价于op(p,q),也就是说中缀标注符op被用作抽取器模式。
===函数
函数定义:
定义函数时,除了递归函数以外,你能够省略返回值类型声明,scala会根据=号后边的表达式的类型推断返回值类型,同时=号后边表达式的值就是函数的返回值,你无需使用return语句(scala推荐你使用表达式值代替return返回值,固然根据你的须要,也能够显式使用return返回值)。示例以下:
def abs(x: Double) = if(x >= 0) x else -x
def fac(n: Int) = {
var r = 1
for(i <- 1 to n) r = r * i
r
}
对于递归函数必须指定返回值类型,以下:
def fac(n: Int) : Int = if(n <= 0 ) 1 else n * fac(n-1)
但你要知道的是:声明函数返回类型,老是有好处的,它可使你的函数接口清晰。所以建议不要省略函数返回类型声明。
函数体定义时有“=”时,若是函数仅计算单个结果表达式,则能够省略花括号。若是表达式很短,甚至能够把它放在def的同一行里。
去掉了函数体定义时的“=”的函数通常称之为“过程”,过程函数的结果类型必定是Unit。所以,有时定义函数时忘记加等号,结果经常是出乎你的意料的。
没有返回值的函数的默认返回值是Unit。
函数调用:
scala中,方法调用的空括号能够省略。惯例是若是方法带有反作用就加上括号,若是没有反作用就去掉括号。若是在函数定义时,省略了空括号,那么在调用时,就不能加空括号。另外,函数做为操做符使用时的调用形式参见相应部分。
函数参数:
通常状况下,scala编译器是没法推断函数的参数类型的,所以你须要在参数列表中声明参数的类型。对于函数字面量来讲,根据其使用环境的不一样,scala有时能够推断出其参数类型。
scala里函数参数的一个重要特征是它们都是val(这是无需声明的,在参数列表里你不能显式地声明参数变量为val),不是var,因此你不能在函数里面给参数变量从新赋值,这将遭到编译器的强烈反对。
重复参数:
在scala中,你能够指明函数的最后一个参数是重复的,从而容许客户向函数传入可变长度参数列表。要想标注一个重复参数,可在参数的类型以后放一个星号“*”。例如:
def echo(args: String*) = for(arg <- args) println(arg)
这样的话,echo就能够被零至多个String参数调用。在函数内部,重复参数的类型是声明参数类型的数组。所以,echo函数里被声明为类型“String*”的args的类型其实是Array[String]。然而,若是你有一个合适类型的数组,并尝试把它看成重复参数传入,会出现编译错误。要实现这个作法,你须要在数组名后添加一个冒号和一个_*符号,以告诉编译器把数组中的每一个元素看成参数,而不是将整个数组看成单一的参数传递给echo函数,以下:
echo(arr: _*)
默认参数与命名参数:
函数的默认参数与java以及c++中类似,都是从左向右结合。另外,你也能够在调用时指定参数名。示例以下:
def fun(str: String, left: String = “[”, right: String = “]”) = left + str + right
fun(“hello”)
fun(“hello”, “<<<”)
fun(“hello”, left = “<<<”)
函数与操做符:
从技术层面上来讲,scala没有操做符重载,由于它根本没有传统意义上的操做符。诸如“+”、“-”、“*”、“/”这样的操做符,其实调用的是方法。方法被看成操做符使用时,根据使用方式的不一样,能够分为:中缀标注(操做符)、前缀标注、后缀标注。
中缀标注:中缀操做符左右分别有一个操做数。方法若只有一个参数(其实是两个参数,由于有一个隐式的this),调用的时候就能够省略点及括号。实际上,若是方法有多个显式参数,也能够这样作,只不过你须要把参数用小括号所有括起来。若是方法被看成中缀操做符来使用(也即省略了点及括号),那么左操做数是方法的调用者,除非方法名以冒号“:”结尾(此时,方法被右操做数调用)。另外,scala的中缀标注不只能够在操做符中存在,也能够在模式匹配、类型声明中存在,参见相应部分。
前缀标注:前缀操做符只有右边一个操做数。可是对应的方法名应该在操做符字符上加上前缀“unary_”。标识符中能做为前缀操做符用的只有+、-、!和~。
后缀标注:后缀操做符只有左边一个操做数。任何不带显式参数的方法均可以做为后缀操做符。
在scala中,函数的定义方式除了做为对象成员函数的方法以外,还有内嵌在函数中的函数,函数字面量和函数值。
嵌套定义的函数:
嵌套定义的函数也叫本地函数,本地函数仅在包含它的代码块中可见。
函数字面量:
在scala中,你不只能够定义和调用函数,还能够把它们写成匿名的字面量,也即函数字面量,并把它们做为值传递。函数字面量被编译进类,并在运行期间实例化为函数值(任何函数值都是某个扩展了scala包的若干FunctionN特质之一的类的实例,如Function0是没有参数的函数,Function1是有一个参数的函数等等。每个FunctionN特质有一个apply方法用来调用函数)。所以函数字面量和值的区别在于函数字面量存在于源代码中,而函数值做为对象存在于运行期。这个区别很像类(源代码)和对象(运行期)之间的关系。
如下是对给定数执行加一操做的函数字面量:
(x: Int) => x + 1
其中,=>指出这个函数把左边的东西转变为右边的东西。在=>右边,你也可使用{}来包含代码块。
函数值是对象,所以你能够将其存入变量中,这些变量也是函数,你可使用一般的括号函数调用写法调用它们。如:
val fun = (x: Int) => x + 1
val a = fun(5)
有时,scala编译器能够推断出函数字面量的参数类型,所以你能够省略参数类型,而后你也能够省略参数外边的括号。如:
(x) => x + 1
x => x + 1
若是想让函数字面量更简洁,能够把通配符“_”看成单个参数的占位符。若是碰见编译器没法识别参数类型时,在“_”以后加上参数类型声明便可。如:
List(1,2,3,4,5).filter(_ > 3)
val fun = (_: Int) + (_: Int)
部分应用函数:
你还可使用单个“_”替换整个参数列表。例如能够写成:
List(1,2,3,4,5).foreach(println(_))
或者更好的方法是你还能够写成:
List(1,2,3,4,5).foreach(println _)
以这种方式使用下划线时,你就正在写一个部分应用函数。部分应用函数是一种表达式,你不须要提供函数须要的全部参数,代之以仅提供部分,或不提供所需参数。以下先定义一个函数,而后建立一个部分应用函数,并保存于变量,而后该变量就能够做为函数使用:
def sum(a: Int, b: Int, c: Int) = a + b + c
val a = sum _
println(a(1,2,3))
实际发生的事情是这样的:名为a的变量指向一个函数值对象,这个函数值是由scala编译器依照部分应用函数表达式sum _,自动产生的类的一个实例。编译器产生的类有一个apply方法带有3个参数(之因此带3个参数是由于sum _表达式缺乏的参数数量为3),而后scala编译器把表达式a(1,2,3)翻译成对函数值的apply方法的调用。你可使用这种方式把成员函数和本地函数转换为函数值,进而在函数中使用它们。不过,你还能够经过提供某些但不是所有须要的参数表达一个部分应用函数。以下,此变量在使用的时候,能够仅提供一个参数:
val b = sum(1, _: Int, 3)
若是你正在写一个省略全部参数的部分应用函数表达式,如println _或sum _,并且在代码的那个地方正须要一个函数,你就能够省略掉下划线(不是须要函数的地方,你这样写,编译器可能会把它看成一个函数调用,由于在scala中,调用无反作用的函数时,默认不加括号)。以下代码就是:
List(1,2,3,4,5).foreach(println)
闭包:
闭包是能够包含自由(未绑定到特定对象)变量的代码块;这些变量不是在这个代码块内或者任何全局上下文中定义的,而是在定义代码块的环境中定义(局部变量)。好比说,在函数字面量中使用定义在其外的局部变量,这就造成了一个闭包。以下代码foreach中就建立了一个闭包:
var sum = 0
List(1,2,3,4,5).foreach(x => sum += x)
在scala中,闭包捕获了变量自己,而不是变量的值。变量的变化在闭包中是可见的,反过来,若闭包改变对应变量的值,在外部也是可见的。
尾递归:
递归调用这个动做在最后的递归函数叫作尾递归。scala编译器能够对尾递归作出重要优化,当其检测到尾递归就用新值更新函数参数,而后把它替换成一个回到函数开头的跳转。
你可使用开关“-g:notailcalls”关掉编译器的尾递归优化。
别高兴太早,scala里尾递归优化的局限性很大,由于jvm指令集使实现更加先进的尾递归形式变得困难。尾递归优化限定了函数必须在最后一个操做调用自己,而不是转到某个“函数值”或什么其余的中间函数的状况。
在scala中,你不要刻意回避使用递归,相反,你应该尽可能避免使用while和var配合实现的循环。
高阶函数:
带有其余函数做为参数的函数称为高阶函数。
柯里化:
柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,而且返回接受余下的参数且返回结果的新函数的技术。以下就是一个柯里化以后的函数:
def curriedSum(x: Int)(y: Int) = x + y
这里发生的事情是当你调用curriedSum时,实际上接连调用了两个传统函数。第一个调用的函数带单个名为x的参数,并返回第二个函数的函数值;这个被返回的函数带一个参数y,并返回最终计算结果。你可使用部分应用函数表达式方式,来获取第一个调用返回的函数,也即第二个函数,以下:
val onePlus = curriedSum(3)_
高阶函数和柯里化配合使用能够提供灵活的抽象控制,更进一步,当函数只有一个参数时,在调用时,你可使用花括号代替小括号,scala支持这种机制,其目的是让客户程序员写出包围在花括号内的函数字面量,从而让函数调用感受更像抽象控制,不过须要注意的是:花括号也就是块表达式,所以你能够在其中填写多个表达式,可是最后一个表达式的值做为该块表达式的值并最终成为了函数参数。若是函数有两个以上的参数,那么你可使用柯里化的方式来实现函数。
传名参数:
对于以下代码,myAssert带有一个函数参数,该参数变量的类型为不带函数参数的函数类型:
myAssert(predicate: () => Boolean) = {
if(!predicate())
throw new AssertionError
}
在使用时,咱们须要使用以下的语法:
myAssert(() => 5 > 3)
这样很麻烦,咱们可使用以下称之为“传名参数”的语法简化之:
myAssert(predicate: => Boolean) = {
if(!predicate)
throw new AssertionError
}
以上代码在定义参数类型时是以“=>”开头而不是“() =>”,并在调用函数(经过函数类型的变量)时,不带“()”。如今你就能够这样使用了:
myAssert(5 > 3)
其中,“predicate: => Boolean”说明predicate是函数类型,在使用时传入的是函数字面量。注意与“predicate: Boolean”的不一样,后者predicate是Boolean类型的(表达式)。
偏函数:
偏函数和部分应用函数是无关的。偏函数是只对函数定义域的一个子集进行定义的函数。 scala中用scala.PartialFunction[-T, +S]来表示。偏函数主要用于这样一种场景:对某些值如今还没法给出具体的操做(即需求还不明朗),也有可能存在几种处理方式(视乎具体的需求),咱们能够先对需求明确的部分进行定义,之后能够再对定义域进行修改。PartialFunction中可使用的方法以下:
isDefinedAt:判判定义域是否包含指定的输入。
orElse:补充对其余域的定义。
compose:组合其余函数造成一个新的函数,假设有两个函数f和g,那么表达式f _ compose g _则会造成一个f(g(x))形式的新函数。你可使用该方法对定义域进行必定的偏移。
andThen:将两个相关的偏函数串接起来,调用顺序是先调用第一个函数,而后调用第二个,假设有两个函数f和g,那么表达式f _ andThen g _则会造成一个g(f(x))形式的新函数,恰好与compose相反。
===类(class)和对象(object)
类(class)和构造器:
类的定义形式以下:
class MyClass(a: Int, b: Int) {
println(a.toString)
}
在scala中,类也能够带有类参数,类参数能够直接在类的主体中使用,不必定义字段而后把构造器的参数赋值到字段里,但须要注意的是:类参数仅仅是个参数而已,不是字段,若是你须要在别的地方使用,就必须定义字段。不过还有一种称为参数化字段的定义形式,能够简化字段的定义,以下:
class MyClass(val a: Int, val b: Int) {
println(a.toString)
}
以上代码中多了val声明,做用是在定义类参数的同时定义类字段,不过它们使用相同的名字罢了。类参数一样可使用var做前缀,还可使用private、protected、override修饰等等。scala编译器会收集类参数并创造出带一样的参数的类的主构造器,并将类内部任何既不是字段也不是方法定义的代码编译至主构造器中。除了主构造器,scala也能够有辅助构造器,辅助构造器的定义形式为def this(…)。每一个辅助构造器都以“this(…)”的形式开头以调用本类中的其余构造器,被调用的构造器能够是主构造器,也能够是源文件中早于调用构造器定义的其余辅助构造器。其结果是对scala构造器的调用终将致使对主构造器的调用,所以主构造器是类的惟一入口点。在scala中,只有主构造器能够调用超类的构造器。
你能够在类参数列表以前加上private关键字,使类的主构造器私有,私有的主构造器只能被类自己以及伴生对象访问。
可使用require方法来为构造器的参数加上先决条件,若是不知足要求的话,require会抛出异常,阻止对象的建立。
若是类的主体为空,那么能够省略花括号。
访问级别控制:
公有是scala的默认访问级别,所以若是你想使成员公有,就不要指定任何访问修饰符。公有的成员能够在任何地方被访问。
私有相似于java,即在以前加上private。不一样的是,在scala中外部类不能够访问内部类的私有成员。
保护相似于java,即在以前加上protected。不一样的是,在scala中同一个包中的其余类不能访问被保护的成员。
scala里的访问修饰符能够经过使用限定词强调。格式为private[X]或protected[X]的修饰符表示“直到X”的私有或保护,这里X指代某个所属的包、类或单例对象。
scala还有一种比private更严格的访问修饰符,即private[this]。被private[this]标记的定义仅能在包含了定义的同一个对象中被访问,这种限制被称为对象私有。这能够保证成员不被同一个类中的其余对象访问。
对于私有或者保护访问来讲,scala的访问规则给予了伴生对象和类一些特权,伴生对象能够访问全部它的伴生类的私有成员、保护成员,反过来也成立。
成员(类型、字段和方法):
scala中也能够定义类型成员,类型成员以关键字type声明。经过使用类型成员,你能够为类型定义别名。
scala里字段和方法属于相同的命名空间,scala禁止在同一个类里用一样的名称定义字段和方法,尽管java容许这样作。
getter和setter:
在scala中,类的每一个非私有的var成员变量都隐含定义了getter和setter方法,可是它们的命名并无沿袭java的约定,var变量x的getter方法命名为“x”,它的setter方法命名为“x_=”。你也能够在须要的时候,自行定义相应的getter和setter方法,此时你还能够不定义关联的字段,自行定义setter的好处之一就是你能够进行赋值的合法性检查。
若是你将scala字段标注为@BeanProperty时,scala编译器会自动额外添加符合JavaBeans规范的形如getXxx/setXxx的getter和setter方法。这样的话,就方便了java与scala的互操做。
样本类:
带有case修饰符的类称为样本类(case class),这种修饰符可让scala编译器自动为你的类添加一些句法上的便捷设定,以便用于模式匹配,scala编译器自动添加的句法以下:
① 帮你实现一个该类的伴生对象,并在伴生对象中提供apply方法,让你不用new关键字就能构造出相应的对象;
② 在伴生对象中提供unapply方法让模式匹配能够工做;
③ 样本类参数列表中的全部参数隐式地得到了val前缀,所以它们被看成字段维护;
④ 添加toString、hashCode、equals、copy的“天然”实现。
封闭类:
带有sealed修饰符的类称为封闭类(sealed class),封闭类除了类定义所在的文件以外不能再添加任何新的子类。这对于模式匹配来讲是很是有用的,由于这意味着你仅须要关心你已经知道的子类便可。这还意味你能够得到更好的编译器帮助。
单例对象(singleton object):
scala没有静态方法,不过它有相似的特性,叫作单例对象,以object关键字定义(注:main函数也应该在object中定义,任何拥有合适签名的main方法的单例对象均可以用来做为程序的入口点)。定义单例对象并不表明定义了类,所以你不可使用它来new对象。当单例对象与某个类共享同一个名称时,它就被称为这个类的伴生对象(companion object)。类和它的伴生对象必须定义在同一个源文件里。类被称为这个单例对象的伴生类。类和它的伴生对象能够互相访问其私有成员。不与伴生类共享名称的单例对象被称为独立对象(standalone object)。
apply与update:在scala中,一般使用相似函数调用的语法。当使用小括号传递变量给对象时,scala都将其转换为apply方法的调用,固然前提是这个类型实际定义过apply方法。好比s是一个字符串,那么s(i)就至关于c++中的s[i]以及java中的s.charAt(i),实际上 s(i) 是 s.apply(i) 的简写形式。相似地,BigInt(“123”) 就是 BigInt.apply(“123”) 的简写形式,这个语句使用伴生对象BigInt的apply方法产生一个新的BigInt对象,不须要使用new。与此类似的是,当对带有括号并包含一到若干参数的变量赋值时,编译器将使用对象的update方法对括号里的参数(索引值)和等号右边的对象执行调用,如arr(0) = “hello”将转换为arr.update(0, “hello”)。
类和单例对象之间的差异是,单例对象不带参数,而类能够。由于单例对象不是用new关键字实例化的,因此没机会传递给它实例化参数。单例对象在第一次被访问的时候才会被初始化。当你实例化一个对象时,若是使用了new则是用类实例化对象,无new则是用伴生对象生成新对象。同时要注意的是:咱们能够在类或(单例)对象中嵌套定义其余的类和(单例)对象。
对象相等性:
与java不一样的是,在scala中,“==”和“!=”能够直接用来比较对象的相等性,“==”和“!=”方法会去调用equals方法,所以通常状况下你须要覆盖equals方法。若是要判断引用是否相等,可使用eq和ne。
在使用具备哈希结构的容器类库时,咱们须要同时覆盖hashCode和equals方法,可是实现一个正确的hashCode和equals方法是比较困难的一件事情,你须要考虑的问题和细节不少,能够参见java总结中的相应部分。另外,正如样本类部分所讲的那样,一旦一个类被声明为样本类,那么scala编译器就会自动添加正确的符合要求的hashCode和equals方法。
===抽象类和抽象成员
与java类似,scala中abstract声明的类是抽象类,抽象类不能够被实例化。
在scala中,抽象类和特质中的方法、字段和类型均可以是抽象的。示例以下:
trait MyAbstract {
type T // 抽象类型
def transform(x: T): T // 抽象方法
val initial: T // 抽象val
var current: T // 抽象var
}
抽象方法:抽象方法不须要(也不容许)有abstract修饰符,一个方法只要是没有实现(没有等号或方法体),它就是抽象的。
抽象类型:scala中的类型成员也能够是抽象的。抽象类型并非说某个类或特质是抽象的(特质自己就是抽象的),抽象类型永远都是某个类或特质的成员。
抽象字段:没有初始化的val或var成员是抽象的,此时你须要指定其类型。抽象字段有时会扮演相似于超类的参数这样的角色,这对于特质来讲尤为重要,由于特质缺乏可以用来传递参数的构造器。所以参数化特质的方式就是经过在子类中实现抽象字段完成。如对于如下特质:
trait MyAbstract {
val test: Int
println(test)
def show() {
println(test)
}
}
你可使用以下匿名类语法建立继承自该特质的匿名类的实例,以下:
new MyAbstract {
val test = 1
}.show()
你能够经过以上方式参数化特质,可是你会发现这和“new 类名(参数列表)”参数化一个类实例仍是有区别的,由于你看到了对于test变量的两次println(第一次在特质主体中,第二次是因为调用了方法show),输出了两个不一样的值(第一次是0,第二次是1)。这主要是因为超类会在子类以前进行初始化,而超类抽象成员在子类中的具体实现的初始化是在子类中进行的。为了解决这个问题,你可使用预初始化字段和懒值。
预初始化字段:
预初始化字段,可让你在初始化超类以前初始化子类的字段。预初始化字段用于对象或有名称的子类时,形式以下:
class B extends {
val a = 1
} with A
预初始化字段用于匿名类时,形式以下:
new {
val a = 1
} with A
须要注意的是:因为预初始化的字段在超类构造器调用以前被初始化,所以它们的初始化器不能引用正在被构造的对象。
懒值:
加上lazy修饰符的val变量称为懒值,懒值右侧的表达式将直到该懒值第一次被使用的时候才计算。若是懒值的初始化不会产生反作用,那么懒值定义的顺序就不用多加考虑,由于初始化是按需的。
===继承与覆盖(override)
继承:
继承时,若是父类主构造器带有参数,子类须要把要传递的参数放在父类名以后的括号里便可,以下:
class Second(a: Int, b: Int) extends First(a) {…}
scala继承层级:
如上图所示:Any是全部其余类的超类。Null是全部引用类(继承自AnyRef的类)的子类,Null类型的值为null。Nothing是全部其余类(包括Null)的子类,Nothing类型没有任何值,它的一个用处是它标明了不正常的终止(例如抛出异常,啥也不返回)。AnyVal是scala中内建值类(共9个)的父类。AnyRef是scala中全部引用类的父类,在java平台上AnyRef实际就是java.lang.Object的别名,所以java里写的类和scala里写的类都继承自AnyRef,你能够认为java.lang.Object是scala在java平台上实现AnyRef的方式。scala类与java类的不一样之处在于,scala类还继承了一个名为ScalaObject的特别记号特质,目的是想让scala程序执行得更高效。
覆盖:
因为scala里字段和方法属于相同的命名空间,这让字段能够覆盖无参数方法或空括号方法,但反过来好像不能够啊。另外,你也能够用空括号方法覆盖无参数方法,反之亦可。在scala中,若子类覆盖了父类的具体成员则必须带override修饰符;如果实现了同名的抽象成员时则override是可选的;若并未覆盖或实现基类中的成员则禁用override修饰符。
===特质(trait)
特质至关于接口,不能被实例化。特质定义使用trait关键字,与类类似,你一样能够在其中定义而不只是声明字段和方法等。你可使用extends或with将多个特质“混入”类中。注意当在定义特质时,使用extends指定了特质的超类,那么该特质就只能混入扩展了指定的超类的类中。
特质与类的区别在于:①特质不能带有“类参数”,也即传递给主构造器的参数;②不论在类的哪一个地方,super调用都是静态绑定的,但在特质中,它们是动态绑定的,由于在特质定义时,尚且不知道它的超类是谁,由于它尚未“混入”,因为在特质中使用super调用超类方法是动态绑定的,所以你须要对特质中相应的方法加上abstract声明(虽然加上了abstract声明,但方法仍能够被具体定义,这种用法只有在特质中有效),以告诉编译器特质中的该方法只有在特质被混入某个具备期待方法的具体定义的类中才有效。你须要很是注意特质被混入的次序:特质在构造时顺序是从左到右,构造器的顺序是类的线性化(线性化是描述某个类型的全部超类型的一种技术规格)的反向。因为多态性,子类的方法最早起做用,所以越靠近右侧的特质越先起做用,若是最右侧特质调用了super,它调用左侧的特质的方法,依此类推。
Ordered特质:
Ordered特质扩展自java的Comparable接口。Ordered特质用于排序,为了使用它,你须要作的是:首先将其混入类中,而后实现一个compare方法。须要注意的是:Ordered并无为你定义equals方法,由于经过compare实现equals须要检查传入对象的类型,可是由于类型擦除,致使它没法作到。所以,即便继承了Ordered,也仍是须要本身定义equals。
Ordering特质:
Ordering特质扩展自java的Comparator接口。Ordering特质也用于排序,为了使用它,你须要作的是:定义一个该特质的子类的单独的实例,须要实现其中的compare方法,并将其做为参数传递给排序函数。此乃策略模式也。
Application特质:
特质Application声明了带有合适签名的main方法。可是它存在一些问题,因此只有当程序相对简单而且是单线程的状况下才能够继承Application特质。Application特质相对于APP特质来讲,有些陈旧,你应该使用更新的APP特质。
APP特质:
APP特质同Application特质同样,都提供了带有合适签名的main方法,在使用时只需将它混入你的类中,而后就能够在类的主构造器中写代码了,无需再定义main方法。若是你须要命令行参数,能够经过args属性获得。
===显式类型转换
正如以前所述的,scala中类型转换使用方法实现,如下是显式类型测试和显式类型转换的示例:
a.isInstanceOf[String] // 显式类型测试
a.asInstanceOf[String] // 显式类型转换
===隐式转换、隐式参数
隐式转换:
隐式转换只是普通的方法,惟一特殊的地方是它以修饰符implicit开始,implicit告诉scala编译器能够在一些状况下自动调用(好比说若是当前类型对象不支持当前操做,那么scala编译器就会自动添加调用相应隐式转换函数的代码,将其转换为支持当前操做的类型的对象,前提是已经存在相应的隐式转换函数且知足做用域规则),而无需你去调用(固然若是你愿意,你也能够自行调用)。隐式转换函数定义以下:
implicit def functionName(…) = {…}
隐式转换知足如下规则:
做用域规则:scala编译器仅会考虑处于做用域以内的隐式转换。隐式转换要么是以单一标识符的形式(即不能是aaa.bbb的形式,应该是bbb的形式)出如今做用域中,要么是存在于源类型或者目标类型的伴生对象中。
单一调用规则:编译器在同一个地方只会添加一次隐式操做,不会在添加了一个隐式操做以后再在其基础上添加第二个隐式操做。
显式操做先行规则:若编写的代码类型检查无误,则不会尝试任何隐式操做。
隐式参数:
柯里化函数的完整的最后一节参数能够被隐式提供,即隐式参数。此时最后一节参数必须被标记为implicit(整节参数只需一个implicit,并非每一个参数都须要),同时用来提供隐式参数的相应实际变量也应该标记为implicit的。对于隐式参数,咱们须要注意的是:
① 隐式参数也能够被显式提供;
② 提供隐式参数的实际变量必须以单一标识符的形式出如今做用域中;
③ 编译器选择隐式参数的方式是经过匹配参数类型与做用域内的值类型,所以隐式参数应该是很稀少或者很特殊的类型(最好是使用自定义的角色肯定的名称来命名隐式参数类型),以便不会被碰巧匹配;
④ 若是隐式参数是函数,编译器不只会尝试用隐式值补足这个参数,还会把这个参数看成可用的隐式操做而使用于方法体中。
视界:
视界使用“<%”符号,能够用来缩短带有隐式参数的函数签名。好比,“T <% Ordered[T]”是在说“任何的T都好,只要T能被看成Ordered[T]便可”,所以只要存在从T到Ordered[T]的隐式转换便可。
注意视界与上界的不一样:上界“T <: Ordered[T”是说T是Ordered[T]类型的。
隐式操做调试:
隐式操做是scala的很是强大的特性,但有时很难用对也很难调试。
有时若是编译器不能发现你认为应该能够用的隐式转换,你能够把该转换显式地写出来,这有助于发现问题。
另外,你能够在编译scala程序时,使用“-Xprint:typer”选项来让编译器把添加了全部的隐式转换以后的代码展现出来。
===类型参数化
在scala中,类型参数化(相似于泛型)使用方括号实现,如:Foo[A],同时,咱们称Foo为高阶类型。若是一个高阶类型有2个类型参数,则在声明变量类型时可使用中缀形式来表达,此时也称该高阶类型为中缀类型,示例以下:
class Foo[A,B]
val x: Int Foo String = null // Int Foo String 等同于 Foo[Int,String]
与java类似,scala的类型参数化也使用类型擦除实现(类型擦除是不好劲的泛型机制,不过多是因为java的缘由,scala也这样作了),类型擦除的惟一例外就是数组,由于在scala中和java中,它们都被特殊处理,数组的元素类型与数组值保存在一块儿。在scala中,数组是“不变”的(这点与java不一样),泛型默认是“不变”的。
协变、逆变与不变:
拿Queue为例,若是S是T的子类型,那么Queue[S]是Queue[T]的子类型,就称Queue是协变的;相反,若是Queue[T]是Queue[S]的子类型,那么Queue是逆变的;既不是协变又不是逆变的是不变的,不变的又叫严谨的。
在scala中,泛型默认是不变的。当定义类型时,你能够在类型参数前加上“+”使类型协变,如Queue[+A]。相似地,你能够在类型参数前加上“-”使类型逆变。在java中使用类型时能够经过使用extends和super来达到协变逆变的目的,它们都是“使用点变型”,java不支持“声明点变型”。而scala中同时提供了声明点变型(“+”和“-”,它们只能在类型定义时使用)和使用点变型(“<:”和“>:”,相似于java中的extends和super,在使用类型时声明)。无论是“声明点变型”仍是“使用点变型”,都遵循PECS法则,详见java泛型。须要注意的是:变型并不会被继承,父类被声明为变型,子类若想保持仍须要再次声明。
继承中的协变逆变:
c++、java、scala都支持返回值协变,也就是说在继承层次中子类覆盖超类的方法时,能够指定返回值为更具体的类型。c#不支持返回值协变。
容许参数逆变的面向对象语言并很少——c++、java、scala和c#都会把它当成一个函数重载。
更多信息参见java泛型。
===集合
scala的集合(collection)库分为可变(mutable)类型与不可变(immutable)类型。以Set为例,特质scala.collection.immutable.Set和scala.collection.mutable.Set都扩展自scala.collection.Set。
scala集合的顶层抽象类和特质:
scala.collection.immutable:
scala.collection.mutable:
不可变集合与可变集合之间的对应关系:
不可变(collection.immutable._) |
可变(collection.mutable._) |
Array |
ArrayBuffer |
List |
ListBuffer |
String |
StringBuilder |
- |
LinkedList, DoubleLinkedList |
List |
MutableList |
Queue |
Queue |
Array |
ArraySeq |
Stack |
Stack |
HashMap HashSet |
HashMap HashSet |
- |
ArrayStack |
Iterable与Iterator:
Iterable是可变和不可变序列、集、映射的超特质。集合对象能够经过调用iterator方法来产生迭代器Iterator。Iterable与Iterator之间的差别在于:前者指代的是能够被枚举的类型,然后者是用来执行枚举操做的机制。尽管Iterable能够被枚举若干次,但Iterator仅能使用一次。
数组:
在scala中,数组保存相同类型的元素,其中包含的元素值是可变的。数组也是对象,访问数组使用小括号。在JVM中,scala的数组以java数组方式实现。scala中数组是非协变的。
定长数组使用Array,建立以后长度不可改变。变长数组使用ArrayBuffer。
与java同样,scala中多维数组也是经过数组的数组来实现的。构造多维数组可使用ofDim方法或者直接使用for循环来new。示例以下:
val matrix = Array.ofDim[Double](3,4) // ofDim方法建立多维数组
matrix(1)(2) = 12.36
val mutliarr = new Array[Array[Int]](10) // for循环方式建立多维数组
for(i <- 0 until mutliarr.length)
mutliarr(i) = new Array[Int](5)
列表:
列表保存相同类型的元素。scala里的列表类型是协变的,这意味着若是S是T的子类,那么List[S]也是List[T]的子类。
不可变列表使用List,一旦建立以后就不可改变。可变列表使用ListBuffer。
List是抽象类,它有两个子类型:Nil和::。Nil是空列表对象,类型是List[Nothing]。::是样本类,能够建立非空列表,::的伴生对象能够以中缀标注的形式用于模式匹配。因此在scala中存在两个::,一个是样本类,另外一个是List的方法,所以在构造一个列表时,咱们就有了多种方法,以下:
val list1 = List("A") // 这里List是伴生对象,至关于 List.apply()
val list2 = ::("A",Nil) // 这里::是伴生对象, 至关于 ::.apply()
val list3 = "A" :: Nil // 这里::是方法, 至关于 Nil.::()
List类没有提供append操做(向列表尾部追加),由于随着列表变长,效率将逐渐低下。List提供了“::”作前缀插入,由于这将消耗固定时间。若是你想经过添加元素来构造列表,你的选择是先把它们前缀插入,完成以后再调用reverse;或者使用ListBuffer,一种提供append操做的可变列表,完成以后调用toList。
栈和队列:
scala集合库提供了可变和不可变的栈类Stack,也提供了可变和不可变的队列类Queue。
元组与对偶:
元组Tuple也是不可变的,但元组能够包含不一样类型的元素,而且所以而不能继承自Iterable。
元组实例化以后,可使用点号、下划线和从1开始的索引访问其中的元素。由于元组能够保存不一样类型的元素,因此不能使用apply方法访问其元素(apply返回一样的类型)。元组的索引从1开始,是由于对于拥有静态类型元组的其余语言,如Haskell和ML,从1开始是传统的设定。
scala的任何对象均可以调用“->”方法,并返回包含键值对的二元组(也叫对偶,是元组的最简单形态),好比 “hello” -> 100 则建立出 (“hello”, 100)。
元组相应操做示例以下:
val t = (1400, “Jim”, “haha”, 3.14) // 定义一个元组
val second = t._2 // 引用元组第二个组元
val (first, second, third, fourth) = t // 分别获取元组的第一、二、三、4个组元
val (first, secong, _) = t // 只获取前两个组元
集和映射:
集中保存着不重复的元素。映射能够把键和值关联起来保存。
拉链操做:
val symbols = Array(“<”, “-”, “>”)
val counts = Array(2, 10, 2)
val pairs = symbols.zip(counts)
以上代码生成对偶类型的数组,以下:
Array((<,2), (-,10), (>,2))
可变集合vs不可变集合:
可变集合性能更好,不可变集合更易于理清头绪。对于某些问题来讲,可变集合可以很好的处理;而另外一些,不可变集合更为合适。若是在使用可变集合时,你发现须要担心什么时候复制可变集合的副本,或者思考不少关于谁“主宰”或“拥有”可变集合的时候,那么请考虑是否可用不可变集合代替。
===异常
scala的异常工做机制与java的相似,但也有区别。区别以下:
① scala没有“受检”异常——你不须要声明函数或方法可能会抛出某种异常。
② throw表达式是有值的,其值是Nothing类型。
③ try-catch-finally表达式也是有值的,可是状况有些特殊。当没有抛出异常时,try子句为表达式值;若是抛出异常并被捕获,则对应于相应的catch子句;若是没有被捕获,表达式就没有返回值。finally子句计算获得的值,老是被抛弃(除非使用return语句),因此你应该在finally子句中干一些它应该干的事,好比说:关闭文件、套接字、数据库链接等,而最好别干什么其余事。
===断言、检查
scala里,断言使用assert函数,检查使用ensuring函数,若是条件不成立,它们将会抛出AssertionError。它们都在Predef中定义。你可使用JVM的-ea和-da命令行标志来开放和禁止断言以及检查。
===包和引用
打包:
scala的代码采用了java平台完整的包机制。你可使用两种方式把代码放进包里:
① 使用放在文件顶部的package子句来把整个文件放入包中;
② 使用package子句把要放入到包中的代码用花括号括起来,这种方式像C#的命名空间。使用这种方式,你能够定义出嵌套的包,注意:scala的包能够嵌套,java则不能够。任何你本身写的顶层包都被隐含地包含在_root_包中,所以你能够在多层嵌套的包代码中经过_root_来访问顶层包中的代码。
引用:
与java相似,scala使用import来引用,与java不一样的是,scala的import子句:
① 能够出如今任何地方,而不只仅在文件开始处;
② 能够引用对象和包;
③ 能够重命名或隐藏一些被引用的成员。这能够经过在被引用成员的对象以后加上括号里的引用选择器子句来作到,示例以下(令p为包名):
import p.{x} // 从p中引入x,等价于 import p.x
import p.{x => y} // 从p中引入x,并重命名为y
import p.{x => _, _} // 从p中引入除了x以外的全部东东。注意单独的“_”称做全包括,必须位于选择器的最后。import p.{_} 等价于 import p._
隐式引用:
scala隐含地为每一个源文件都加入以下引用:
import java.lang._
import scala._
import Predef._
包scala中的Predef对象包含了许多有用的方法。例如:一般咱们所使用的println、readLine、assert等。
===scala I/O
因为scala能够和java互操做,所以目前scala中的I/O类库并很少,你可能须要使用java中的I/O类库。下面介绍scala中有的东东:
scala.Console对象能够用于终端输入输出,其中终端输入函数有:readLine、readInt、readChar等等,终端输出函数有:print、println、printf等等。其实,Predef对象中提供的预约义的readLine、println等等方法都是Console对象中对应方法的别名。
scala.io.Source能够以文本的方式迭代地读取源文件或者其余数据源。用完以后记得close啊。
对象序列化:
为了让对象可序列化,你能够这样定义类:
@SerialVersionUID(42L) class Person extends Serializable {…}
其中,@SerialVersionUID注解指定序列化ID,若是你能接受缺省的ID,也可省去该注解;Serializable在scala包中,所以你无需引入。你能够像java中同样对对象进行序列化。scala集合类都是能够序列化的,所以你能够把它们做为你的可序列化类的成员。
===Actor和并发
与java的基于共享数据和锁的线程模型不一样,scala的actor包则提供了另一种不共享任何数据、依赖消息传递的模型。设计并发软件时,actor是首选的工具,由于它们可以帮助你避开死锁和争用情况,这两种情形都是在使用共享和锁模型时很容易遇到的。
建立actor:
actor是一个相似于线程的实体,它有一个用来接收消息的邮箱。实现actor的方法是继承scala.actors.Actor特质并完成其act方法。你能够经过actor的start方法来启动它。actor在运行时都是相互独立的。你也可使用scala.actors.Actor对象的actor方法来建立actor,不过此时你就无需再调用start方法,由于它在建立以后立刻启动。
发送接收消息:
Actor经过相互发送消息的方式进行通讯,你可使用“!”方法来发送消息,使用receive方法来接收消息,receive方法中包含消息处理的模式匹配(偏函数)。发送消息并不会致使actor阻塞,发送的消息在接收actor的邮箱中等待处理,直到actor调用了receive方法,若是actor调用了receive但没有模式匹配成功的消息,那么该actor将会阻塞,直到收到了匹配的消息。建立actor并发送接收消息的示例以下:
object ScalaTest extends Actor {
def act() {
while (true) {
receive {
case msg => println(msg)
}
}
}
def main(args: Array[String]) {
start()
this ! "hello."
}
}
将原生线程看成actor:
Actor子系统会管理一个或多个原生线程供本身使用。只要你用的是你显式定义的actor,就不须要关心它们和线程的对应关系是怎样的。该子系统也支持反过来的情形:即每一个原生线程也能够被看成actor来使用。此时,你应该使用Actor.self方法来将当前线程做为actor来查看,也就是说能够这样使用了:Actor.self ! "message"。
经过重用线程获取更好的性能:
Actor是构建在普通java线程之上的,若是你想让程序尽量高效,那么慎用线程的建立和切换就很重要了。为帮助你节约线程,scala提供了React方法,和receive同样,react带有一个偏函数,不一样的是,react在找到并处理消息后并不返回(它的返回类型是Nothing),它在处理完消息以后就结束了。因为react不须要返回,故其不须要保留当前线程的调用栈。所以actor库能够在下一个被唤醒的线程中重用当前的线程。极端状况下,若是程序中全部的actor都使用react,则它们能够用单个线程实现。
因为react不返回,接收消息的消息处理器如今必须同时处理消息并执行actor全部余下的工做。一般的作法是用一个顶级的工做方法(好比act方法自身)供消息处理器在处理完消息自己以后调用。编写使用react而非receive的actor颇具挑战性,不过在性能上可以带来至关的回报。另外,actor库提供的Actor.loop函数能够重复执行一个代码块,哪怕代码调用的是react。
良好的actor风格:
良好的actor风格的并发编程可使你的程序更容易调试而且减小死锁和争用情况。下面是一些actor风格编程的指导意见:
actor不该阻塞:编写良好的actor在处理消息时并不阻塞。由于阻塞可能会致使死锁,即其余多个actor都在等待该阻塞的actor的响应。代替阻塞当前actor,你能够建立一个助手actor,该助手actor在睡眠一段时间以后发回一个消息,以告诉建立它的actor。记住一句话:会阻塞的actor不要处理消息,处理消息的actor请不要使其阻塞。
只经过消息与actor通讯:Actor模型解决共享数据和锁的关键方法是提供了一个安全的空间——actor的act方法——在这里你能够顺序地思考。换个说法就是,actor让你能够像一组独立的经过异步消息传递来进行交互的单线程的程序那样编写多线程的程序。不过,这只有在消息是你的actor的惟一通讯途径的前提下才成立。一旦你绕过了actor之间的消息传递机制,你就回到了共享数据和锁的模型中,全部那些你想用actor模型避开的困难又都回来了。但这并非说你应该彻底避免绕开消息传递的作法,虽然共享数据和锁要作正确很难,但也不是彻底不可能。实际上scala的actor和Erlang的actor实现方式的区别之一就是,scala让你能够在同一个程序中混用actor与共享数据和锁两种模型。
优选不可变消息:actor模型提供了每一个actor的act方法的单线程环境,你无需担忧它使用到的对象是不是线程安全的,由于它们都被局限于一个线程中。但例外的是在多个线程中间传送的消息对象,它被多个actor共享,所以你须要担忧消息对象是否线程安全。确保消息对象是线程安全的最佳途径是在消息中只使用不可变对象。若是你发现你有一个可变对象,而且想经过消息发送给另外一个actor,你应该制做并发送它的一个副本。
让消息自包含:actor在发送请求消息以后不该阻塞,它继续作着其余事情,当收到响应消息以后,它如何解释它(也就是说它如何能记起它在发送请求消息时它在作着什么呢),这是一个问题。解决办法就是在响应消息中增长冗余信息,好比说能够把请求消息中的一些东东做为响应消息的一部分发送给请求者。
===GUI编程
scala图形用户界面编程可使用scala.swing库,该库提供了对java的Swing框架的GUI类的访问,对其进行包装,隐藏了大部分复杂度。示例以下:
object MyScalaGUI extends SimpleSwingApplication {
def top = new MainFrame {
title = "My Scala GUI"
location = new Point(600, 300)
preferredSize = new Dimension(400, 200)
val button = new Button {
text = "Click me"
}
val label = new Label {
text = "Who am I?"
}
contents = new BoxPanel(Orientation.Vertical) {
contents += button
contents += label
}
listenTo(button)
reactions += {
case ButtonClicked(b) => label.text = "Hello, I'm Tom."
}
}
}
要编写图形界面程序,你能够继承SimpleSwingApplication类,该类已经定义了包含一些设置java Swing框架代码的main方法,main方法随后会调用top方法,而top方法是抽象的,须要你来实现。top方法应当包含定义顶级GUI组件的代码,这一般是某种Frame。
GUI类库:
Frame:便可以包含任意数据的窗体。Frame有一些属性(也就是getter和setter),其中比较重要的有title(将被写到标题栏)、contents(将被显示在窗体中)。Frame继承自Container,每一个Container都有一个contents属性,让你得到和设置它包含的组件,不过,Frame的contents只能包含一个组件,由于框架的contents属性只能经过“=”赋值,而有些Container (如Panel)的contents能够包含多个组件,由于它们的contents属性能够经过“+=”赋值。
MainFrame:就像是个普通的Swing的Frame,只不过关闭它的同时也会关闭整个GUI应用程序。
Panel:面板是根据某种固定的布局规则显示全部它包含的组件的容器。面板能够包含多个组件。
处理事件:
为了处理事件,你须要为用户输入事件关联一个动做,scala和java基本上用相同的“发布/订阅”方式来处理事件。发布者又称做事件源,订阅者又称做事件监听器。举例来讲,Button是一个事件源,它能够发布一个事件ButtonClicked,表示该按钮被点击。scala中事件是真正的对象,建立一个事件也就是建立一个样本类的实例,样本类的参数指向事件源。事件(样本)类包含在scala.swing.event包中。
在scala中,订阅一个事件源source的方法是调用listenTo(source),取消订阅的方法是调用deafTo(source)。好比:你可让一个组件A监听它其中的一个组件B,以便A在B发出任何事件时获得通知。为了让A对监听到的事件作出响应,你须要向A中名为reactions的属性添加一个处理器,处理器也就是带有模式匹配的函数字面量,能够在单个处理器中用多个样原本匹配多种事件。可以使用“+=”向reactions中添加处理器,使用“-=”从中移除处理器。从概念上讲,reactions中安装的处理器造成一个栈,当接收到一个事件时,最后被安装的处理器首先被尝试,但无论尝试是否成功,后续的处理器都会被一一尝试。
===结合scala和java
scala和java高度兼容,所以能够进行互操做,大多数状况下结合这两种语言时并不须要太多顾虑,尽管如此,有时你仍是会遇到一些结合java和scala的问题。基本上,scala使用java代码相对于java使用scala代码更容易一些。
scala代码如何被翻译:
scala的实现方式是将代码翻译为标准的java字节码。scala的特性尽量地直接映射为相对等的java特性。但scala中的某些特性(如特质)在java中没有,而还有一些特性(如泛型)与java中的不尽相同,对于这些特性,scala代码没法直接映射为java的语法结构,所以它必须结合java现有的特性来进行编码。这些是通常性的原则,如今咱们来考虑一些特例:
值类型:相似Int这样的值类型翻译成java有两种不一样的方式,只要可能就直接翻译为java的int以得到更好的性能,但有时作不到,就翻译为包装类的对象。
单例对象:scala对单例对象的翻译采用了静态和实例方法相结合的方式。对每个scala单例对象,编译器都会为这个对象建立一个名称后加美圆符号的java类,这个类拥有scala单例对象的全部方法和字段,这个java类同时还有一个名为MODULE$的静态字段,保存该类在运行期建立的一个实例。也就是说,在java中要这样使用scala中的单例对象:单例对象名$.MODULE$.方法名();
特质:编译任何scala特质都会建立一个同名的java接口,这个接口能够做为java类型使用,你能够经过这个类型的变量来调用scala对象的方法。若是特质中还有已经实现的方法,那么还会生成对应的实现类“特质名$class”。
存在类型:
全部java类型在scala中都有对等的概念,这是必要的,以便scala能够访问任何合法的java类。scala中的存在类型实际上主要是用于从scala访问java泛型中的通配类型以及没有给出类型参数的原始类型。存在类型的通用形式以下:
type forSome { declarations }
type部分是任意的scala类型,而declarations部分是一个抽象val和type的列表。这个定义解读为:声明的变量和类型是存在但未知的,正如类中的抽象成员那样。这个类型进而被容许引用这些声明的变量和类型,虽然编译器不知道它们具体指向什么。例如对于以下的java语句:
Iterator<?>
Iterator<? extends Component>
在scala中能够用存在类型分别表示为:
Iterator[T] forSome { type T }
Iterator[T] forSome { type T <: Component }
另外,存在类型也可使用下划线占位符语法以更简短的方式来写。若是在可使用类型的地方使用了下划线,那么scala会为你作出一个存在类型,每一个下划线在forSome语句中都变成一个类型参数。如前咱们在介绍scala的泛型时,指定类型参数上界下界时,使用的就是这种简写的语法。做为示例,咱们能够将上述两个语句简写为:
Iterator[_]
Iterator[_ <: Component]
隐式转换为java类型:
在scala和java代码之间传递数据时,若是使用的是容器类库(固然包括数组等),那么可能须要进行一些转换,而scala类库自己就提供了这样的隐式转换库,所以你能够方便地在scala和java之间传递数据。你惟一须要作的是:import scala.collection.JavaConversions._。