第1章 可伸展的語言
Scala應用范圍廣,從編寫腳本,到建立大型系統。
運行在標準Java平臺上,與Java庫無縫交互。
更能發揮力量的地方:建立大型系統或可重用控件的架構。
將面向對象和函數式編程加入到靜態類型語言。
在Scala中,函數就是對象。函數類型是能夠被子類繼承的類。
Scala是純粹的面向對象語言:每個值都是對象,每個操作都是方法調用。如1+1,實際是調用Int里的+方法。
?
語言分類:
指令式語言 imperative
函數式語言 functional
函數式編程:
函數是對能產生值的表達式的定義。
兩種指導理念:
1、函數是頭等值。
函數也是值,與整數、字符串處于同一個地位;
可以當作參數傳遞給其他函數;
可以當作結果從函數中返回或保存在變量里;
可以在函數里定義其他函數,就好像在函數里定義整數一樣;
可以定義匿名函數,并隨意插入到代碼的任何地方。
2、程序的操作應該把輸入值映射為輸出值而不是就地修改數據。
不可變數據結構是函數式語言的一塊基石;
Scala中的不可變列表元組、映射表、集;
無副作用
?
Scala的兼容性:
Scala程序被編譯成JVM字節碼,性能與Java相當。
自由調用Java類庫。
允許定義類型失配或者選用不存在的方法時使用隱式類型轉換(implicit conversion)。
?
Scala定義類:?
class MyClass(index:Int, name:String)
含有兩個私有字段,以及一個構造函數。
測試只能證明錯誤存在,而非不存在。
有用的文檔是那些程序的讀者自己不可能很容易的看懂程序功能的文檔。
?
類型推斷 type inference
?
第2章?Scala入門初探第2章?Scala入門初探
Scala解釋器:
resX 解釋器自動產生的計算結果
val 只能賦值一次
var 可以賦值多次
?
兩次回車鍵取消多行輸入
?
函數定義:
def max(x:Int, y:Int):Int={function block;
}
參數類型必須指定。
函數結果(result type)類型 ?如果遞歸定義則必須指定,否則可以不用顯示指定而用類型推斷。
?
如果函數只有一行表達式,可省略{}
當函數沒有返回值時返回Unit。
結果類型為Unit的函數是為了其運行效果,即side effect。
?
腳本:經常會被執行的,放在文件中的子序列。
> scala hello.scala arguments
腳本中訪問腳本參數:args(0)
args.foreach(arg=>println(arg))
args.foreach(println)
函數字面量語法結構:括號、命名參數列表、右箭頭、函數體
(x:Int, y:Int) => x+y
for(arg <- args)println(arg)
第3章 Scala入門再探
3.1 類型參數化數組
val greetStrings=new Array[String](3)
greetStrings(0)="Hello"
greetStrings(1)=", "
greetStrings(2)="world!\n"val greetStrings = Array("Hello",", ","world!\n")
for(i<-0 to 2)print(greetString(i))
1、用[String]新建一個String型的數組;
2、數組用(index)引用其中的值;
3、若方法只有一個參數,調用時可以省略點及括號;
(0).to(2) ?0 to 2 ??????println(10) ?Console println 10
4、Scala沒有傳統意義上的操作符,取而代之的是+-*/這樣的字符作為方法名,1+2 是方法的調用:(1).+(2)
?
用括號訪問數組:數組也是對象,實質為對象的值參數,調用的是apply方法。
用括號傳遞給變量一個或多個值參數時,scala會把它轉換成對apply方法的調用。
任何對于對象的值參數應用都被轉換為對apply方法的調用。前提是這個類型定義了apply方法。
arrayObject(i)被轉換為arrayObject.apply(i)
?
當對帶有括號并包括一到若干參數的變量賦值時,編譯器將使用對象的update方法對括號里的參數和等號右邊的對象執行調用。
greetStrings(0)=”Hello”
greetStrings.update(0,”Hello”)
?
val numNames = Array(“zero”,”one”,”two”)
val numNames = Array.apply(“zero”,”one”,”two”)
調用apply工廠方法,可以有不定個數的參數,定義在Array的伴生對象(companion object)中。
?
3.2 使用列表(list)
方法沒有副作用是函數式編程的重要概念,計算并返回值應該是方法的唯一目的。
數組是可變的同類對象序列。
列表是不可變的同類對象序列,為的是方便函數式編程。
val oneTwo=List(1,2)
val threeFour=List(3,4)
val all=oneTwo::threeFour
val all=1::2::3::4::Nil
:::列表疊加,新建列表并返回
::前插 ???Nil是空列表 ???帶冒號的方法調用者是后者,1::Nil ??Nil.::(1)
如何append?
1、先::前插,再reverse;
2、ListBuffer支持append,在toList。
?
3.3 使用元組(Tuple)
不可變,可包含不同類型的元素。
如果在方法里返回多個對象,java的做法是創建JavaBean以包含多個返回值,Scala里可以返回元組來達到目的。
val pair=(99,"Luftballons")
println(pair._1+" "+pair._2)
用點號、下劃線和基于1
的索引訪問其中的元素。
列表的apply方法始終返回同樣的類型,所以可以用(index)訪問,而元組中元素的類型都不同,所以用._index訪問。
從1開始是依傳統設定的。
?
3.4 使用集(set)和映射(map)
scala.collection.immutable.Set
scala.collection.mutable.Set
scala.collection.imutable.HashMap
scala.collection.mutable.HashMap
默認為immutable。
?
3.5 學習識別函數式風格
如果包含var變量,可能是指令式風格;
如果僅含val變量,可能是函數式風格。
?
指令式風格
def printArgs(args :Array[String]):Unit={var i = 0while(i<args.length){println(args(i))i+=1}
}
函數式風格
def printArgs(args:Array[String]):Unit={for(arg<-args)println(args) //args.foreach(println)
每個有用的程序都會有某種形式的副作用,否則就不可能向程序之外提供什么有價值的東西。
?
無副作用的代碼使測試變得容易:
def formatArgs(args:Array[String])=args.mkString("\n")
val res=formatArgs(Array("zero","one","two"))
assert(res=="zero\none\ntwo")
Scala的assert方法檢查傳入的Boolean表達式,如果為假,拋出AssertionError。
?
Scala程序員的平衡感:
崇尚val、不可變對象、無副作用的方法;
只有在特定需要和加以權衡之后才選擇var、可變對象、有副作用的方法。
?
3.6 從文件里讀取文本行(腳本)
import scala.io.Source def widthOfLength(s:String)=s.length.toString.length if(args.length>0){val lines=Source.fromFile(args(0)).getLines.toListval longestLine=lines.reduceLeft((a,b)=>if(a.length>b.length)a else b)val maxWidth=widthOfLength(longestLine)for(line<-lines){val numSpaces=maxWidth-widthOfLength(line)val padding=" "*numSpacesprint(padding+ling.length+"|"+line) } elseConsole.err.println("Please enter filename")
Source.fromFile(args(0)).getLines方法返回一個迭代器Iterator[String]。
getLines方法返回的是迭代器,一旦遍歷完成就失效了,所以要toList。
第4章 類和對象
4.1 類、字段、方法
成員(member):
val或var定義字段
def定義方法
?
字段的另一種說法:實例變量(instance variable)
?
私有字段只能被同一個類里的方法訪問,所有能更新字段的代碼都被鎖定在類里。
聲明字段私有:private關鍵字
Public是Scala的默認訪問級別。
class ChecksumAccumulator{private var sum=0def add(b:Byte):Unit=sum+=b //def add(b:Byte){sum+=b}def checksum():Int=~(sum&0xFF)+1
}
Scala里方法參數都是val,即C++的常引用。
如果沒有顯式返回語句,Scala方法將返回方法中最后一次計算得到的值。
如果函數只有一行表達式,可省略{}。
對結果類型為Unit的方法來說,執行的目的就是為了它的副作用——能夠改變方法之外的某處狀態或執行I/O活動。
過程(procedure)——一種僅為了副作用而執行的方法。
定義函數時,沒有等號,返回的是Unit??def add(b:Byte){sum+=b}
?
4.2 分號推斷
語句末尾的分號通常可選,如果一行包含多條語句,必須加分號。
如val s = “hello”; println(s)
除非以下情況之一成立,否則行尾被認為是一個分號。
1、疑問行由一個不能合法作為語句結尾的字結束,如句點或中綴操作符;
2、下一行開始于不能作為語句開始的詞;
3、行結束于()或[]內部,因為這些符號不能容納多個語句。
?
4.3 Singleton對象
Scala不能定義靜態成員,取而代之的是定義單例對象(singleton object)。
用object關鍵字替換class關鍵字。
import scala.collection.mutable.Map
object ChecksumAccumulator{private val cache=Map[String,Int]()def calculate(s:String):Int=if(cache.contains(s))cache(s) //返回鍵s對應的值else{val acc=new ChecksumAccumulaterfor(c<-s)acc.add(c.toByte)val cs=acc.checksum()cache+=(s->cs)cs}
}
單例對象,對應于java的靜態方法工具類。
當單例對象與某個類共享同一個名稱時,被稱為這個類的伴生對象(companion object)。
同名類的伴生對象,與伴生類(companion class)在一個源文件中。
類和其伴生對象可以互相訪問其私有成員。
?
單例對象名.方法名 ?CheckSumAccumulator.calculate(“Every value is an object”)
單例對象不是類型,它擴展了伴生類的父類并可以混入特質。
可以使用類型調用單例對象,或者類型的實例變量指代單例對象,并把它傳遞給需要類型參數的方法。
單例對象不帶參數,而類可以。因為單例對象不是new實例化的,沒機會傳遞給它實例化參數。
每個單例對象都被實現為虛構類(synthetic class)的實例,并指向靜態的變量。
虛構類的名稱是對象名后加一個美元符,單例對象ChecksumAccumulator的虛構類是ChecksumAccumulator$。
單例對象在第一次訪問時被初始化。
?
沒有伴生類的單例對象被稱為獨立對象(standalone object),可用于相關功能方法的工具類,或者定義Scala應用的入口點。
?
4.4 Scala程序
能獨立運行的Scala程序,必須創建有main方法的單例對象——僅一個參數Array[String],且結果類型為Unit。
?
以定義結尾的都不是腳本。
腳本:以結果表達式結束。
?
$ scalac ChecksumAccumulator.scala Summer.scala ?或者
快速Scala編譯器,fast Scala compiler,將編譯程序加載入后臺
$ fsc ChecksumAccumulator.scala Summer.scala
$ fsc -shutdown
?
import ChechsumAcculator.calculate
object Summer{def main(args:Array[String]){for(arg<-grgs)println(arg+":"+calculate(arg))}
}
Scala源文件默認引用java.lang、scala、單例對象Predef。
println語句即為Predef單例對象的println(Predef.println轉而調用Console.println)。
assert實際調用Predef.assert。
?
4.5 Application特質
import ChecksumAcculator.calculate
object FallWinterSpringSummer extends Application{for(season<-List("fall","winter","spring","summer"))println(season+":"+calculate(season))
}
特質Application聲明了帶有合適簽名的main方法,并被單例對象繼承。
大括號之間的代碼被收集進了單例對象的主構造器(primary constructor),并在類初始化時執行。
Scala提供scala.Application特質,可以減少輸入工作。
但無法訪問命令行參數,無法應用于多線程。
?
數組Array 可變同類對象序列
列表List 不可變同類對象序列
元組Tuple 不可變非同類對象序列
集合Set ?映射Map 有可變和不可變
?
第5章 基本類型和操作
5.1 基本類型和操作
整數類型 integral type
Byte
Short
Int
Long
Char
數類型 numeric type
Float
Double
?
String ?屬于java.lang包
Boolean
?
5.2 字面量
字面量literal——直接寫在代碼里的常量值。
?
原始字符串:raw string 三個雙引號對之間的字符串,字符串中可以包含任意字符,不用轉義。
?“””|line1
????|line2”””.stripMargin 此方法使結果符合預期
?
符號字面量——除了顯示名字,什么都不能做。
符號字面量被寫成’<標識符>
被映射成預定義類scala.Symbol的實例,即被編譯器擴展為工廠方法調用:Symbol(“標識符”)。
應用場景:動態類型語言中使用一個標識符。
>val s = ‘aSymbol
>s.name ?#aSymbol
如果同一個符號字面量出現兩次,那么兩個字面量指向的是同一個Symbol對象。
?
5.3 操作符和方法
操作符實際只是普通方法調用的另一種表現形式。
值類型對象調用方法的簡寫形式。
任何方法都可以被當做操作符來標注。
>val s=”hello, world”
>s.indexof(‘o’)
>s indexof ‘o’
中綴標注
前綴標注 ?-2.0 實際為 (2.0).unary_- ???+ - ! ~四種操作符可被用作前綴標注
后綴標注 ?s toLowerCase ?不用點號或括號調用不帶任何參數的方法
?
中綴是二元的,前綴和后綴是一元的。
?
在Scala中,方法調用的空括號,有副作用就加上,沒副作用就省略。
?
5.7 對象的相等性
首先檢查左側是否為null,如果不是,調用左操作數的equals方法。
精確比較取決于左操作數的equals方法定義。
Java中的==對基本類型,比較的是值相等性;對于引用類型,比較的是引用相等性,即是否指向JVM堆里的同一個對象。
5.8 操作符的關聯性
任何以:字符結尾的方法,右操作數調用方法,傳入左操作數 a:::b ??b.:::(a)
其余相反。
?
5.9 富包裝器
Scala基本類型的方法很多。
Scala基本類型都隱式轉換(implicit conversion)為富包裝器類型,可對外提供多種額外的方法。
想要看到基本類型的所有可用方法,可以查看基本類型的富包裝器的API文檔。
?
第6章?函數式對象
本章重點:函數式對象——不具有任何可改變狀態的對象的類。
不可變對象的優點:
1、對象狀態不可變,思路清晰;
2、無線程安全問題;
3、讓哈希鍵值更安全。
缺點:需要賦值很大的對象表,可變對象可以原址更新。
6.1 類Rational的規格說明書
有理數:rational number ?狀態不可變,兩個有理數運算后將產生新的有理數。
分子:numerator
分母:denominator
約分:normalized
?
6.2 創建Rational
class Rational(n:Int,d:Int)
如果類沒有主體,不需要花括號。
n,d稱為類參數(class parameter)。
Scala編譯器會收集這兩個類參數并創造出一個主構造器(primary constructor)。
類內部任何既不是字段又不是方法的代碼將被編譯進主構造器中,每次新建實例時執行。
class Rational(n:Int,d:Int){println("Create"+n+"/"+d)
}
6.3 重新實現toString方法
類默認繼承了java.lang.Object類的toString實現,打印 ?類名@地址。
重寫(override)toString方法:
class Rational(n:Int,d:Int){override def toStirng=n+"/"+d
}
?
6.4 檢查先決條件
分子不能為零
先決條件是對傳遞給方法或構造器的值的限制,是調用者必須滿足的需求。
為主構造器定義先決條件(precondition):
使用require方法:(定義在scala包的獨立對象Predef上)
class Rational(n:Int,d:Int){require(d!=0)override def toStirng=n+"/"+d
}
Require方法帶一個boolean參數,如果為真,正常返回;反之,require將拋出IllegalArgumentException阻止對象被構造。
?
6.5 添加字段
類參數是為了給字段賦值,所以要添加字段。
class Rational(n:Int,d:Int){required(d!=0)val numer:Int=nval denom:Int=doverride toString=numer+"/"+denomdef add(that:Rational):Rational=new Rational(numer*that.denom+that.num*denom,denom*that.denom)
}
添加了兩個字段,并用參數初始化他們。
?
6.6 自指向
關鍵字this指向當前執行方法被調用的對象實例。
def lessThan(that:Rational)=this.numer*that.denom<that.num*this.denom
6.7 輔助構造器Auxiliary constructor
輔助構造器都是以this(...)形式開頭。
每個輔助構造器的第一個動作都是調用同類的別的構造器。
被調用的構造器可以是主構造器,也可以是別的輔助構造器。
每個Scala構造器最終調用主構造器。主構造器是類的唯一入口點。
?
6.8 私有字段和方法
最大公約數:greatest common divisor
初始化器:initializer——對字段進行初始化的代碼n/g ?d/g。
?
6.9 定義操作符
直接將add替換為+
?
?
6.10 Scala的標識符
1、字母數字式 alphanumeric identifier ?
字母、下劃線開頭,之后跟字母數字下劃線。
$為編譯系統所用的標識符,用戶程序不應包含。不建議用“_”結尾
駝峰式camel case:類和特質開頭大寫,其余開頭小寫。
Java中常量名大寫且用“_”分隔,Scala中的常量也用駝峰式。
2、操作符 operator identifier
Scala編譯器將操作符標識符轉換成合法的內嵌”$”的java標識符。
如:->將被內部表達為$colon$minus$greater。
3、混合標識符 mixed identifier
由字母數字組成,后面跟著下劃線和一個操作標識符。
如unary_+ 前綴
myvar_=被用作定義賦值操作符的方法名,是由編譯器產生的用來支持屬性(property)的。
4、字面量標識符 literal identifier
用反引號包括的任意字符串`...`。
可以把運行時環境認可的任意字符串放在反引號之間當作標識符,結果總被當作是Scala標識符。即使反引號中的是保留字也有效。
在java的Thread類中訪問靜態的yield方法是典型應用:
因為yield是Scala的保留字,所以不能寫成Thread.yield(),可以寫成Thread.`yield`()。
?
6.11 方法重載 overload
?
6.12 隱式轉換
類似于C++的轉換構造函數。實際定義在伴生對象中。
implicit def intToRational(x:Int)=new Rational(x)
此時,可執行操作:
>val r=new Rational(2,3)
>2*r ?//將2轉換為Rational
注意:要隱式轉換起作用,需要將隱式轉換函數定義在作用范圍之內。
隱式轉換函數是隱式調用。
編程時要在簡潔性和可讀性之間進行權衡。
?