“大道泛兮,其可左右。
万物恃之而生而不辞,功成不名有。
衣养万物而不为主,常无欲,可名于小;万物归焉而不为主,可名为大。
是以圣人之能成大也,以其不为大也,故能成大。”1
本文的出发点是记录scala与传统java以及python的不同之处。
行结尾方式 抑或 换行符
跟java不同的地方在于,每一行不必以“;”结尾,除了一个行如果写多条语句时,前面的语句必须以“;”结尾。
1 | val rdd = sc.parallelize([1,2,3,4]);println(rdd) |
注释
完全跟java一样,/**/用于多行;//用于单行。
package
引入包,有两种方式:
- 跟java相同
1package com.c-goshine.sh.demo; - 有些类似c#使用这种方式可以在一个文件中定义
1package com.cgoshine.sh.demo{23}多个包。import
跟python有些像,import可以写在文件的任何地方。
导入某个包下的所有成员,这个跟java的方式有些不同,用“_”来代替java的“*”,如:1import com.cgoshine.sh.demo._ //引入包内所有成员
如果想引用包里的几个成员,可以使用选择器selector
import java.awt.{Color, Font}
// 重命名成员
import java.util.{HashMap => JavaHashMap}
// 隐藏成员
import java.util.{HashMap => _, _} // 引入了util包的所有成员,但是HashMap被隐藏了
注意:默认情况下,Scala 总会引入 java.lang._ 、 scala._ 和 Predef._,这里也能解释,为什么以scala开头的包,在使用时都是省去scala.的。
数据类型
scala与java有着相同的数据类型。
| 数据类型 | 描述 |
|---|---|
| Byte | 8位有符号补码整数。数值区间为 -128 到 127 |
| Short | 16位有符号补码整数。数值区间为 -32768 到 32767 |
| Int | 32位有符号补码整数。数值区间为 -2147483648 到 2147483647 |
| Long | 64位有符号补码整数。数值区间为 -9223372036854775808 到 9223372036854775807 |
| Float | 32位IEEE754单精度浮点数 |
| Double | 64位IEEE754单精度浮点数 |
| Char | 16位无符号Unicode字符, 区间值为 U+0000 到 U+FFFF |
| String | 字符序列 |
| Boolean | true或false |
| Unit | 表示无值,和其他语言中void等同。用作不返回任何结果的方法的结果类型。Unit只有一个实例值,写成()。 |
| Null | null 或空引用 |
| Nothing | Nothing类型在Scala的类层级的最低端;它是任何其他类型的子类型。 |
| Any | Any是所有其他类的超类 |
| AnyRef | AnyRef类是Scala里所有引用类(reference class)的基类 |
变量声明
用var声明变量;用val声明常量
格式:var VariableName : DataType [= Initial Value]
1 | var ss:String [="rzaor"] |
2 | var ss //裸奔方式 |
访问修饰符
public private protected
- private
默认public,其中private比java要严格(在嵌套类情况下,外层类甚至不能访问被嵌套类的私有成员)。1class Outer{2class Inner{3private def f(){println("f")}4class InnerMost{5f() // 正确6}7}8(new Inner).f() //错误9} - protected
同样也比java的要严格。因为它只允许保护成员在定义了该成员的的类的子类中被访问。而在java中,用protected关键字修饰的成员,除了定义了该成员的类的子类可以访问,同一个包里的其他类也可以进行访问。1package p{2class Super{3protected def f() {println("f")}4}5class Sub extends Super{6f()7}8class Other{9(new Super).f() //错误10}11} - 作用域保护理解为:如果写成private[x],读作”这个成员除了对[…]中的类或[…]中的包中的类及它们的伴生对像
1private[x]2protected[x]可见外,对其它所有类都是private。这种技巧在横跨了若干包的大型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西。类Navigator被标记为private[bobsrockets]就是说这个类对包含在bobsrockets包里的所有的类和对象可见。1package bobsrocckets{2package navigation{3private[bobsrockets] class Navigator{4protected[navigation] def useStarChart(){}5class LegOfJourney{6private[Navigator] val distance = 1007}8private[this] var speed = 2009}10}11package launch{12import navigation._13object Vehicle{14private[launch] val guide = new Navigator15}16}17}
比如说,从Vehicle对象里对Navigator的访问是被允许的,因为对象Vehicle包含在包launch中,而launch包在bobsrockets中,相反,所有在包bobsrockets之外的代码都不能访问类Navigator。
位运算符
位运算符用来对二进制位进行操作,~,&,|,^分别为取反,按位与与,按位与或,按位与异或运算。
如果指定 A = 60; 及 B = 13; 两个变量对应的二进制为:
1 | A = 0011 1100 |
2 | |
3 | B = 0000 1101 |
4 | |
5 | -------位运算---------- |
6 | |
7 | A&B = 0000 1100 |
8 | |
9 | A|B = 0011 1101 |
10 | |
11 | A^B = 0011 0001 |
12 | |
13 | ~A = 1100 0011 |
Scala 中的按位运算法则如下:
| 运算符 | 描述 | 实例 |
|---|---|---|
| & | 按位与运算符 | (a & b) 输出结果 12 ,二进制解释: 0000 1100 |
| | | 按位或运算符 | (a | b) 输出结果 61 ,二进制解释: 0011 1101 |
| ^ | 按位异或运算符 | (a ^ b) 输出结果 49 ,二进制解释: 0011 0001 |
| ~ | 按位取反运算符 | (~a ) 输出结果 -61 ,二进制解释: 1100 0011, 在一个有符号二进制数的补码形式。 |
| << | 左移动运算符 | a << 2 输出结果 240 ,二进制解释: 1111 0000 |
| >> | 右移动运算符 | a >> 2 输出结果 15 ,二进制解释: 0000 1111 |
| >>> | 无符号右移 | A >>>2 输出结果 15, 二进制解释: 0000 1111 |
函数 方法
Scala 有函数和方法,二者在语义上的区别很小。Scala 方法是类的一部分,而函数是一个对象可以赋值给一个变量。换句话来说在类中定义的函数即是方法。
- 函数声明
1def functionName ([参数列表]) : [return type] - 函数定义如果函数没有返回值,可以返回为 Unit,这个类似于 Java 的 void。
1def functionName ([参数列表]) : [return type] = {2function body3return [expr]4}1object add{2def addInt( a:Int, b:Int ) : Int = {3var sum:Int = 04sum = a + b5return sum6}7def printMe( ) : Unit = {8println("Hello, Scala!")9}10}闭包
闭包是一个函数,返回值依赖于声明在函数外部的一个或多个变量。
如下面这段匿名的函数:函数体内有一个变量 i,它作为函数的一个参数。如下面的另一段代码:1val multiplier = (i:Int) => i * 10在 multiplier 中有两个变量:i 和 factor。其中的一个 i 是函数的形式参数,在 multiplier 函数被调用时,i 被赋予一个新的值。然而,factor不是形式参数,而是自由变量,考虑下面代码:1val multiplier = (i:Int) => i * factor这样定义的函数变量 multiplier 成为一个”闭包”,因为它引用到函数外面定义的变量,定义这个函数的过程是将这个自由变量捕获而构成一个封闭的函数。1var factor = 32val multiplier = (i:Int) => i * factor1object Test {2def main(args: Array[String]) {3println( "muliplier(1) value = " + multiplier(1) )4println( "muliplier(2) value = " + multiplier(2) )5}6var factor = 37val multiplier = (i:Int) => i * factor8}
函数的闭包就是当函数的参数超出其作用域时,我们还能对参数进行访问。
1 | scala> def iscala(content:String) = (message:String) => println(content + " "+ message) |
2 | iscala: (content: String)String => Unit |
3 | |
4 | scala> val func1 = iscala("spark") |
5 | func1: String => Unit = $$Lambda$1228/248295195@3dceec83 |
6 | |
7 | scala> func1("hadoop") |
8 | spark hadoop |
9 | |
10 | scala> func1("hbase") |
11 | spark hbase |
在调用func1时,将之前的content参数的值延续了下来。
字符串连接
1 | string1.concat(string2); |
同样你也可以使用加号(+)来连接
- 创建格式化字符串
1object Test {2def main(args: Array[String]) {3var floatVar = 12.4564var intVar = 20005var stringVar = "scala multi-paradigm!"6var fs = printf("浮点型变量为 " +7"%f, 整型变量为 %d, 字符串为 " +8" %s", floatVar, intVar, stringVar)9println(fs)10}11}for循环
Range 可以是一个数字区间表示 i to j ,或者 i until j。左箭头 <- 用于为变量 x 赋值。1for( var x <- Range ){2statement(s);3}
举个例子:1object Test {2def main(args: Array[String]) {3var a = 0;4// for 循环5for( a <- 1 to 10){6println( "Value of a: " + a );7}8}9}
类和对象
Scala中的类不声明为public,一个Scala源文件中可以有多个类。
1 | class Point(xc: Int, yc: Int) { |
2 | …… |
3 | } |
继承
Scala继承一个基类跟Java很相似, 但我们需要注意以下几点:
- 重写一个非抽象方法必须使用override修饰符。
- 只有主构造函数才可以往基类的构造函数里写参数。
- 在子类中重写超类的抽象方法时,你不需要使用override关键字。
继承会继承父类的所有属性和方法,Scala 只允许继承一个父类。
单例对象 & 伴生对象
Scala单例对象是十分重要的,不像Java一样,有静态类、静态成员、静态方法,但是Scala提供了object对象,这个object对象类似于Java的静态类,它的成员、它的方法都默认是静态的。
如果object的静态成员要被外界访问,则该成员不能被private修饰。
伴生对象首先是一个单例对象,单例对象用关键字object定义。在Scala中,单例对象分为两种,一种是并未自动关联到特定类上的单例对象,称为独立对象 (Standalone Object);另一种是关联到一个类上的单例对象,该单例对象与该类共有相同名字,则这种单例对象称为伴生对象(Companion Object),对应类称为伴生类。
Trait(特性)
Scala Trait(特征) 相当于 Java 的接口,实际上它比接口还功能强大。
与接口不同的是,它还可以定义属性和方法的实现。
一般情况下Scala的类只能够继承单一父类,但是如果是 Trait(特征) 的话就可以继承多个,从结果来看就是实现了多重继承。
Trait(特征) 定义的方式与类类似,但它使用的关键字是 trait。
1 | package com.cgoshine.sh.demo.scala |
2 | |
3 | trait Equal { |
4 | def isEqual(x:Any):Boolean |
5 | def isNotEqual(x:Any):Boolean = !isEqual(x) |
6 | } |
isEqual 方法没有定义方法的实现,isNotEqual定义了方法的实现。子类继承特征可以实现未被实现的方法。所以其实 Scala Trait(特征)更像 Java 的抽象类。
implements这个在scala中到目前为止还没见到,即使“实现”Trait中也是用的extends。
1 | class Point(xc: Int, yc: Int) extends Equal { |
2 | …… |
3 | } |
模式匹配
Scala 提供了强大的模式匹配机制,应用也非常广泛。
match 对应 Java 里的 switch,但是写在选择器表达式之后。即: 选择器 match {备选项}。
一个模式匹配包含了一系列备选项,每个都开始于关键字 case。每个备选项都包含了一个模式及一到多个表达式。箭头符号 => 隔开了模式和表达式。
1 | object Test { |
2 | def main(args: Array[String]) { |
3 | println(matchTest(3)) |
4 | |
5 | } |
6 | def matchTest(x: Int): String = x match { |
7 | case 1 => "one" |
8 | case 2 => "two" |
9 | case _ => "many" |
10 | } |
11 | } |
“_”,默认的全匹配备选项,即没有找到其他匹配时的匹配项,类似 switch 中的 default。
- 使用样例类
使用了case关键字的类定义就是就是样例类(case classes),样例类是种特殊的类,经过优化以用于模式匹配。在声明样例类时,下面的过程自动发生了:1object Test {2def main(args: Array[String]) {3val alice = new Person("Alice", 25)4val bob = new Person("Bob", 32)5val charlie = new Person("Charlie", 32)67for (person <- List(alice, bob, charlie)) {8person match {9case Person("Alice", 25) => println("Hi Alice!")10case Person("Bob", 32) => println("Hi Bob!")11case Person(name, age) =>12println("Age: " + age + " year, name: " + name + "?")13}14}15}16// 样例类17case class Person(name: String, age: Int)18}构造器的每个参数都成为val,除非显式被声明为var,但是并不推荐这么做;
在伴生对象中提供了apply方法,所以可以不使用new关键字就可构建对象;
提供unapply方法使模式匹配可以工作;
生成toString、equals、hashCode和copy方法,除非显示给出这些方法的定义。
正则表达式
Scala 通过 scala.util.matching 包中的 Regex 类来支持正则表达式。
1 | import scala.util.matching.Regex |
2 | |
3 | object Test { |
4 | def main(args: Array[String]) { |
5 | val pattern = "Scala".r |
6 | val str = "Scala is Scalable and cool" |
7 | |
8 | println(pattern findFirstIn str) |
9 | } |
10 | } |
使用 String 类的 r() 方法构造了一个Regex对象,然后使用 findFirstIn 方法找到首个匹配项。
如果需要查看所有的匹配项可以使用 findAllIn 方法。你可以使用 mkString( ) 方法来连接正则表达式匹配结果的字符串,并可以使用管道(|)来设置不同的模式:
1 | import scala.util.matching.Regex |
2 | |
3 | object Test { |
4 | def main(args: Array[String]) { |
5 | val pattern = new Regex("(S|s)cala") // 首字母可以是大写 S 或小写 s |
6 | val str = "Scala is scalable and cool" |
7 | |
8 | println((pattern findAllIn str).mkString(",")) // 使用逗号 , 连接返回结果 |
9 | } |
10 | } |
输出结果为:
1 | $ scalac Test.scala |
2 | $ scala Test |
3 | Scala,scala |
如果你需要将匹配的文本替换为指定的关键词,可以使用 replaceFirstIn( ) 方法来替换第一个匹配项,使用 replaceAllIn( ) 方法替换所有匹配项。
1 | object Test { |
2 | def main(args: Array[String]) { |
3 | val pattern = "(S|s)cala".r |
4 | val str = "Scala is scalable and cool" |
5 | |
6 | println(pattern replaceFirstIn(str, "Java")) |
7 | } |
8 | } |
Scala 的正则表达式继承了 Java 的语法规则,Java 则大部分使用了 Perl 语言的规则。
- 规则:
| 表达式 | 匹配规则 |
|---|---|
| ^ | 匹配输入字符串开始的位置。 |
| $ | 匹配输入字符串结尾的位置。 |
| . | 匹配除”\r\n”之外的任何单个字符。 |
| […] | 字符集。匹配包含的任一字符。例如,”[abc]”匹配”plain”中的”a”。 |
| [^…] | 反向字符集。匹配未包含的任何字符。例如,”[^abc]”匹配”plain”中”p”,”l”,”i”,”n”。 |
| \A | 匹配输入字符串开始的位置(无多行支持) |
| \z | 字符串结尾(类似$,但不受处理多行选项的影响) |
| \Z | 字符串结尾或行尾(不受处理多行选项的影响) |
| re* | 重复零次或更多次 |
| re+ | 重复一次或更多次 |
| re? | 重复零次或一次 |
| re{ n} | 重复n次 |
| re{ n,} | |
| re{ n, m} | 重复n到m次 |
| a|b | 匹配 a 或者 b |
| (re) | 匹配 re,并捕获文本到自动命名的组里 |
| (?: re) | 匹配 re,不捕获匹配的文本,也不给此分组分配组号 |
| (?> re) | 贪婪子表达式 |
| \w | 匹配字母或数字或下划线或汉字 |
| \W | 匹配任意不是字母,数字,下划线,汉字的字符 |
| \s | 匹配任意的空白符,相等于 [\t\n\r\f] |
| \S | 匹配任意不是空白符的字符 |
| \d | 匹配数字,类似 [0-9] |
| \D | 匹配任意非数字的字符 |
| \G | 当前搜索的开头 |
| \n | 换行符 |
| \b | 通常是单词分界位置,但如果在字符类里使用代表退格 |
| \B | 匹配不是单词开头或结束的位置 |
| \t | 制表符 |
| \Q | 开始引号:\Q(a+b)3\E 可匹配文本 “(a+b)3”。 |
| \E | 结束引号:\Q(a+b)3\E 可匹配文本 “(a+b)3”。 |
- 实例:
| 实例 | 描述 |
|---|---|
| . | 匹配除”\r\n”之外的任何单个字符。 |
| [Rr]uby | 匹配 “Ruby” 或 “ruby” |
| rub[ye] | 匹配 “ruby” 或 “rube” |
| [aeiou] | 匹配小写字母 :aeiou |
| [0-9] | 匹配任何数字,类似 [0123456789] |
| [a-z] | 匹配任何 ASCII 小写字母 |
| [A-Z] | 匹配任何 ASCII 大写字母 |
| [a-zA-Z0-9] | 匹配数字,大小写字母 |
| [^aeiou] | 匹配除了 aeiou 其他字符 |
| [^0-9] | 匹配除了数字的其他字符 |
| \d | 匹配数字,类似: [0-9] |
| \D | 匹配非数字,类似: [^0-9] |
| \s | 匹配空格,类似: [ \t\r\n\f] |
| \S | 匹配非空格,类似: [^ \t\r\n\f] |
| \w | 匹配字母,数字,下划线,类似: [A-Za-z0-9_] |
| \W | 匹配非字母,数字,下划线,类似: [^A-Za-z0-9_] |
| ruby? | 匹配 “rub” 或 “ruby”: y 是可选的 |
| ruby* | 匹配 “rub” 加上 0 个或多个的 y。 |
| ruby+ | 匹配 “rub” 加上 1 个或多个的 y。 |
| \d{3} | 刚好匹配 3 个数字。 |
| \d{3,} | 匹配 3 个或多个数字。 |
| \d{3,5} | 匹配 3 个、4 个或 5 个数字。 |
| \D\d+ | 无分组: + 重复 \d |
| (\D\d)+/ | 分组: + 重复 \D\d 对 |
| ([Rr]uby(, )?)+ | 匹配 “Ruby”、”Ruby, ruby, ruby”,等等 |
异常处理
Scala 的异常处理和其它语言比如 Java 类似。
1 | throw new FileNotFoundException |
如果有异常发生,catch字句是按次序捕捉的。因此,在catch字句中,越具体的异常越要靠前,越普遍的异常越靠后。 如果抛出的异常不在catch字句中,该异常则无法处理,会被升级到调用者处。
捕捉异常的catch子句,语法与其他语言中不太一样。在Scala里,借用了模式匹配的思想来做异常的匹配,因此,在catch的代码里,是一系列case字句:
1 | import java.io.FileReader |
2 | import java.io.FileNotFoundException |
3 | import java.io.IOException |
4 | |
5 | object Test { |
6 | def main(args: Array[String]) { |
7 | try { |
8 | val f = new FileReader("input.txt") |
9 | } catch { |
10 | case ex: FileNotFoundException =>{ |
11 | println("Missing file exception") |
12 | } |
13 | case ex: IOException => { |
14 | println("IO Exception") |
15 | } |
16 | } |
17 | } |
18 | } |
- finally 语句
finally 语句用于执行不管是正常处理还是有异常发生时都需要执行的步骤。1import java.io.FileReader2import java.io.FileNotFoundException3import java.io.IOException45object Test {6def main(args: Array[String]) {7try {8val f = new FileReader("input.txt")9} catch {10case ex: FileNotFoundException => {11println("Missing file exception")12}13case ex: IOException => {14println("IO Exception")15}16} finally {17println("Exiting finally...")18}19}20}提取器(Extractor)
提取器是从传递给它的对象中提取出构造该对象的参数。
Scala 提取器是一个带有unapply方法的对象。unapply方法算是apply方法的反向操作:unapply接受一个对象,然后从对象中提取值,提取的值通常是用来构造该对象的值。输出:1object Test {2def main(args: Array[String]) {34println ("Apply 方法 : " + apply("Zara", "gmail.com"));5println ("Unapply 方法 : " + unapply("Zara@gmail.com"));6println ("Unapply 方法 : " + unapply("Zara Ali"));78}9// 注入方法 (可选)10def apply(user: String, domain: String) = {11user +"@"+ domain12}1314// 提取方法(必选)15def unapply(str: String): Option[(String, String)] = {16val parts = str split "@"17if (parts.length == 2){18Some(parts(0), parts(1))19}else{20None21}22}23}1$ scalac Test.scala2$ scala Test3Apply 方法 : Zara.com4Unapply 方法 : Some((Zara,gmail.com))5Unapply 方法 : None
Options Some None
Some和None都是Options的子类,Scala鼓励你在变量和函数返回值可能不会引用任何值的时候使用Option类型。在没有值的时候,使用None;如果有值可以引用,就使用Some来包含这个值。
1 | scala> val capitals = Map("France"->"Paris", "Japan"->"Tokyo", "China"->"Beijing") |
2 | capitals: scala.collection.immutable.Map[String,String] = Map(France -> Paris, Japan -> Tokyo, China -> Beijing) |
3 | |
4 | scala> capitals get "France" |
5 | res0: Option[String] = Some(Paris) |
6 | |
7 | scala> capitals get "Germany" |
8 | res1: Option[String] = None |
当程序回传Some的时候,代表函式返回一个String,你可以调用get()函数获得那个String,如果程序返回的是None,则代表没有字符串可以给你。
在返回None,也就是没有String给你的时候,如果你还硬要调用get()来取得 String 的话,Scala一样是会抛出一个NoSuchElementException异常给你的。
我们也可以选用另外一个方法,getOrElse。这个方法在这个Option是Some的实例时返回对应的值,而在是None的实例时返回传入的参数。换句话说,传入getOrElse的参数实际上是默认返回值。
1 | scala> capitals get "Germany" get |
2 | warning: there was one feature warning; re-run with -feature for details |
3 | java.util.NoSuchElementException: None.get |
4 | …… |
5 | |
6 | scala> capitals get "France" get |
7 | warning: there was one feature warning; re-run with -feature for details |
8 | res3: String = Paris |
9 | |
10 | scala> (capitals get "Germany") getOrElse "Oops" |
11 | res7: String = Oops |
12 | |
13 | scala> capitals get "France" getOrElse "Oops" |
14 | res8: String = Paris |
文件 I/O
Scala 进行文件写操作,直接用的都是 java中 的 I/O 类 (java.io.File):
1 | import java.io._ |
2 | |
3 | object Test { |
4 | def main(args: Array[String]) { |
5 | val writer = new PrintWriter(new File("demo.txt" )) |
6 | |
7 | writer.write("com.cgoshine.sh.demo") |
8 | writer.close() |
9 | } |
10 | } |
- 从屏幕读取输入
1object Test {2def main(args: Array[String]) {3print("input your company's name : " )4val line = Console.readLine56println("thanks,your input is: " + line)7}8} - 从文件读取
从文件读取内容非常简单。我们可以使用 Scala 的 Source 类及伴生对象来读取文件。1import scala.io.Source23object Test {4def main(args: Array[String]) {5println("文件内容为:" )67Source.fromFile("test.txt" ).foreach{8print9}10}11}
1:老子《道德经》第三十四章,老子故里,中国鹿邑。