第一章
- 命令行中使用
:load
命令來加載(編譯并運行)文件(腳本文件):scala> :load example.scala
- 編譯為字節碼文件:
- 腳本:將腳本內容封裝入一個指定的類中,使用
>scalac -Xscript MyClass example.scala
于是會生成MyClass.class
的文件。 - 有package、類描述的代碼文件:使用
>scalac example.scala
。
- 腳本:將腳本內容封裝入一個指定的類中,使用
- 對于生成的字節碼文件,假定文件的包在com.example下,使用
>scala -cp . com.example.MyClass
來運行。 - 如果想要java來編譯生成的scala的字節碼文件,需要用到
scala-library.jar
文件,假定該文件在C:\Users\Berlin\.sbt\boot\scala-2.10.6\lib
下,則寫為>java -cp .;C:\Users\Berlin\.sbt\boot\scala-2.10.6\lib\scala-library.jar MyClass
- val foo定義一個不可變的量,var foo定義了一個可變的量。
- 類的定義:
class Upper { def upper(strings: String*): Seq[String] = { strings.map((s:String) => s.toUpperCase()) } } val up = new Upper println(up.upper("Hello", "World!"))
- 定義了名為Upper的類,其中有名為upper的方法,
- 方法接受String類型參數,參數名為strings,個數為任意個(因為String后有一個星號*)。
- 返回類型為Seq泛型類型(序列),類型為String,相當于Java語法的
Seq<String>
。 - class Upper后沒有參數列表(比如
class Upper(name:String,age:Int){ def upper... }
),因此構造方法沒有參數,所以不用寫括號,直接寫val up = new Upper
。
- 方法定義
def methodName( parameter1 : Type1,parameter2 : Type2 ) : returnedType = { //...method Body }
- 返回類型通常可以省略(遞歸時要寫)
- 在方法體只有一個語句時可以省略花括號
- 方法體最后一句表達式的值就是返回值。
- 返回空可以寫成
...) : Unit = {//......}
- 單例對象
object Upper { def upper(strings: String*) = strings.map(_.toUpperCase()) } println(Upper.upper("Hello", "World!"))
- object關鍵字定義了一個名為Upper的單例對象,
- Scala 運行時只會創建Upper 的一個實例。也就是說,你無法通過new 創建Upper 對象。就好像Java 使用靜態類型一樣
- 形如
( foo : Type ) => foo.method()
或( foo : Type ) => method(a,foo,b,c)
可以簡寫為_.method()
或method(a,_,b,c)
- Scala 不支持靜態類型
- 插值字符串:以s表示:
println( s"Hello ${ this.name } ")
,則變量值會被替換進去。切記以s為標識。 -
case class:
- 支持模式匹配,多用于模式匹配
- 必須有參數列表,就算沒有也要有括號:
case class Clazz(){....
- 參數列表中的參數為public,并且是val,即不可變,可以外部訪問
- case class創建實例時可以不用加new(普通class必須加new)
- 默認實現了toString、hashCode、equals方法
- 默認是可以序列化的,也就是實現了Serializable ;
- 同時創建了伴生object,并實現apply方法
- 更多參考...
- 伴生對象:case class會生成與其同名的單例對象(object),它實現了
apply
方法。這個方法是一個工廠方法,使用case class生成實例時不用new,就是因為scala可以自動尋找apply方法產生一個實例對象。因此,假設Point是一個case class,則這兩句話是等價的:val p1 = Point.apply(1.0, 2.0)
val p2 = Point(1.0, 2.0)
- 可以自己定義伴生對象。任何時候只要對象名(object Clazz {...})和類名(class Clazz {...})相同并且定義在同一個文件中,這些對象就能稱作伴生對象。
在伴生對象中可以定義自己的apply,然后使用類名(參數列表)
即可使用
但是如果這么寫就會有問題:object Singleton { def apply(age:Int,name:String):Unit = println(s"your name is ${name}, age is ${age}"); } Singleton(name="Amy",age=100); //輸出:your name ....
輸出錯誤信息:error: not found: value CaseClass_class CaseClass_{ def apply(s:Int) ={println("good")} } CaseClass_(5)
- equals方法:scala的==會映射為equals方法,即進行值比較(包括對象)。若比較對象的內存地址,則使用
eq
或ne
:obj1 eq obj2
- 嵌套導入:
object Messages { object Exit // 沒有類體 object Finished case class Response(message: String) } class ShapesDrawingActor { import Messages._ //只在這個類范圍內生效:導入Messages對象內容,可以直接使用,如Exit,而不用寫全稱Messages.Exit def ....
- 在Scala 中,main 方法必須為對象方法(object)。(在Java 中,main 方法必須是類靜態方法:
object Test{ //而不是 class Test,否則會提示 CaseClass_.main is not static def main(args: Array[String]) = { //....... } }
第二章
-
變量聲明: val/var name : Type = value
- 不可變:
val array: Array[String] = new Array(5)
- 可變:
var stockPrice: Double = 100.0
變量聲明的同時必須立即初始化。(例外:如構造函數的參數列表:class Person(val name: String, var age: Int)
)
- 不可變:
-
Range: 支持Range 的類型包括Int、Long、Float、Double、Char、BigInt、BigDecimal:
- 1 to 10 : [1,10]
- 1 until 10:[1,10)
- 1 to 10 by 3
- 10 to 1 by -3
- 1L to 10L by 3
- 'a' to 'g' by 3
- BigDecimal(1.1) to BigDecimal(10.3) by 3.1
-
偏函數:在偏函數中只能使用case 語句(處理那些能與至少一個case 語句匹配的輸入,輸入卻與所有語句都不匹配,系統就會拋出一個MatchError),而整個函數必須用花括號包圍。
object CaseClass_{ def main(args: Array[String]) = { var func:PartialFunction[Any,String]={ //輸入任意類型,返回字符串 case s:String=> //匹配String,值賦予s "hello "+s case w:Int => //匹配Int,值賦予w "This is Int" case whatAreYouTalking=> //任意類型,賦予變量whatAreYouTalking "Nothing" } println(func("bbc")); println(func(123)); println(func(3.14)); } }
輸出:
hello bbc This is Int Nothing
在偏函數上調用isDefinedAt方法可以檢測某個實例是否能被該偏函數匹配,返回是布爾值:
func.isDefinedAt(3.14f)
copy 方法:copy 方法也是case 類自動創建的。copy 方法允許你在創建case 類的新實例時只給出與原對象不同部分的參數。例如某case class方法的參數列表有x、y兩個值且都有默認值,則調用copy方法時只寫
p.copy(y=3.14)
,從而創建一個新的實例,它的y是3.14但是x是默認值。-
方法具有多個參數列表:
-
def draw (offset: Point = Point(0, 0)) (f: String => Unit) = {....}
有兩個參數列表 - 使用方法:
draw(Point(1, 2))(str => println("hello")
- 允許把參數列表兩邊的圓括號替換為花括號:
draw(Point(1, 2)){str => println("hello")}
- 使用缺省的參數,第一個圓括號就不能省略:
draw(){str => println("hello")}
- 請注意區分函數體和參數列表,盡管在scala中它們都可用花括號包裹。
-
-
注意無論是不是多參數列表,只有為單一參數時才能大小括號混用,否則只能用小括號:
scala> def s(a:Int)(b:String,c:Double)={ | println(a) | println(b,c) | } scala> s{123}{"das",3.14} //因為第二個列表不是單參數,所以不能用花括號。 <console>:1: error: ';' expected but ',' found. s{123}{"das",3.14} ^
-
多參數列表可以進行類型推斷:
-
def m1(a: Int, f : Int => String) = .....
,則m1(100, i => s"$i + $i")
會提示i的類型沒有給定 -
def m2(a: Int)(f: Int => String) = ...
,則m2(100)(i => s"$i + $i")
就米有錯,Scala可以推斷出i是Int類型。
-
-
方法的定義還可以嵌套:
def outer() = { def inner() : Int ={ ... } inner(); }
內部參數可以屏蔽同名外部參數
-
使用
scala.annotation.tailrec
的tailrec注解來檢查遞歸函數是否實現了尾遞歸,如果沒有則會拋出異常:import scala.annotation.tailrec @tailrec def method(...) : Type = { ...}
-
推斷類型信息
- 在java等靜態語言中,要寫:
HashMap<Integer, String> intToStringMap = new HashMap<Integer, String>();
或者HashMap<Integer, String> intToStringMap = new HashMap<>();
- 在Scala中只用寫:
val intToStringMap: HashMap[Integer, String] = new HashMap
-
val intToStringMap2 = new HashMap[Integer, String]
非顯式類型注解
- 例如,
但是如果寫成def report(name:String)={ val copy = name; // copy沒有寫成 var copy:String =name。因為可以自動推斷出類型 println(s"Your name is ${copy}") } report("Robust") //輸出:Your name is Robust
則會報錯:def report(name:String)={ var copy :String; // 或是 var copy,即不指定類型,但是都沒有初始化 copy = name; println(s"Your name is ${copy}") } report("Robust") //輸出:Your name is Robust
-
var copy;
:error: '=' expected but ';' found. -
var copy :String;
:error: only traits and abstract classes can have declared but undefined members
-
- 在java等靜態語言中,要寫:
-
需要顯式類型注解的情況
- 在類中抽象聲明時,聲明了可變的var 變量或不可變的val 變量,但沒有進行初始化。
- 所有的方法參數(如
def deposit(amount: Money) = {…}
)。注意,如果寫成def deposit(var amount: Money) = ...
或def deposit(val amount: Money) = ...
就會報出兩個錯誤:- error: identifier expected but 'var' found.
-
error: only traits and abstract classes can have declared but undefined members
因此在方法的參數列表中不要寫val或var
- 方法的返回值類型,在以下情況中必須顯式聲明其類型:
- 明顯地使用了return
- 遞歸方法
- 兩個或多個方法重載(擁有相同的函數名),其中一個方法調用了另一個重載方法,調用者需要顯式類型注解。
- Scala 推斷出的類型比你期望的類型更為寬泛,如Any。
-
_*
的用法:設def joiner(strings: String*): String = {....}
函數joiner是一個接受變長參數的函數,參數類型為String。則現在有一個列表strings: List[String]
,為了將其變為分隔的變長參數從而可以適應joiner的參數列表,可以使用:joiner ( strings: _*)
的語法結構。這個可以這么來理解:- 變量標識符后面的冒號表示告訴編譯器這個變量是某種類型
- 下劃線表示類型卻不是指定的,而是根據輸入推斷得出的。Scala 不允許你寫成
strings :String *
,即使你需要為第一個joiner 方法指定的輸入類型就是String。(奇怪) -
*
指示是一個變長列表
所以綜上所述,它就是告訴編譯器這個參數需要由列表類型“拆分”成變長參數列表。
返回類型推斷:最近公共類型。假定某方法不指定返回值,其中有一個
if-else
結構,if中返回List[Int]類型結果,而else返回List[String]結果,則Scala推斷出的返回值類型就是它們的公共父類型,即List[Any]。-
函數和過程:在scala中能夠定義函數。定義的函數可以有返回值,也可以沒有返回值。沒有返回值的叫做過程,有返回值的叫做函數。在語法上的區別是是否有等號:
-
def greeting(name:String){ println(s"Hello ${name}"); 3.14159}
這是一個過程,因為參數列表和花括號之間沒有等號,所以它返回的是Unit
,盡管它會返回一個浮點數3.14159。打印它的結果是不可預知的,通常情況會打印一個()
。例如,println(greeting("David"))
會輸出Hello David
以及()
。 -
def greeting(name:String)={ println(s"Hello ${name}")}
這是一個函數,盡管類型推斷認為它也返回Unit
,并且println(greeting("David"))
輸出結果和上面相同。
-
break 和continue在Scala 中不存在。
若方法中含有某些關鍵字,則使用單引號來表示。比如,
java.util.Scanner.match
,而match是Scala關鍵字,所以要寫java.util.Scanner.`match`