包含內容
- 1基礎
- 2控制結構和函數
- 3數組相關操作
- 4映射和元組
- 5類
- 6對象
- 7.包和引入(x)
- 8繼承
1基礎
- sqrt(2)、pow這些數學函數在scala.math包中定義,當然該包中還包含一些類,不僅有函數。
- import scala.math._ 等于import math._,scala可以省略,代表引入該包下所有東西。
- import java.util.{HashMap => JavaHashMap} //重命名
- import scala.math.{BigInt =>_ , _} 引入除BigInt外的所有成員
- "_" 是scala中通配符,類似java的"*"
- RichInt、RichDouble、RichChar比Int、Double、Char提供了更豐富功能
- apply方法:伴生對象構建對象的常用方法。
- a.方法(b) 等于 a 方法 b 。如a + b = a.+(b)
- StringOps的def count (p :(Char) => Boolean):Int 該方法需要傳入一個接受單個Char并返回true或false的函數,用于指定哪些字符串應當被清點。
- scala底層用java.lang.String類來表示字符串,不過通過StirngOps類給字符串追加了上百種操作,
- "hello".intersect("world") ,這個表達式中java.lang.String對象"hello"被隱式轉換成StringOps對象,接著調用StringOps類的intersect方法。
- Unit類型等同于java或c++中的void
- ScalaDoc文檔中,每個類旁邊的O和C,分別對應類(C)或伴生對象(O)
- scala中沒有++操作,如value++,不允許
2控制結構和函數
REPL
- 在命令行鍵入scala進入REPL模式
- REPL中鍵入:paste可以把代碼粘貼進去,然后Ctrl+D,REPL就會把代碼塊當做一個整體來分析。
- Tab鍵提示
表達式、塊表達式和賦值
scala中表達式(eg:3+4)和語句(eg:if)都有值。
val s = if( x>5) 22 else 2 //if語句給變量賦值
-
塊表達式{},包含一些了表達式,塊中最后一個表達式就是塊的值。
- val distance={ val ds = x-x0; val dy =y -y0; sqrt( dx * dx + dy * dy) } //{}的值取最后一個表達式的值
-
賦值語句的值為Unit,val d = { r = 5 } //此時塊表達式的值為Unit,所以d的值為Unit。
- var y = 3; val x = y = 1 ;//別這樣做,結果:y = 1,但是x = Unit.
輸出:print、println、printf("hello,%s! you are %d years old.\n","liang",23)
輸入:readLine、readInt、readFloat、readBoolean、readChar etc.
循環:
基本語法結構:for( 變量 <- 表達式) ,eg:for( i<- 0.until(s.length) )、for(ch <- "Hello" )
高級for循環:以變量 <- 表達式的形式提供多個生成器,用分號隔開它們,每個生成器都可以帶一個守衛:以if開頭的Boolean表達式。
for( i < 1 to 3 ;j <- 1 to 3 ) print( (10* i +j ) + " ")
//打印:11 12 13 21 22 23 31 32 33 ,每個i都會經歷j<-1 to 3for( i < 1 to 3 ; from = 4 -i ;j <- from to 3 ) print( (10* i +j ) + " ")
//打印: 13 22 23 31 32 33 ,每個i都會經歷from = 4 -i ;每個form都會經過j <- from to 3for( i < 1 to 3 ;j <- 1 to 3 if i != j) print( (10* i +j ) + " ") //帶守衛,注守衛沒有分號
//打印: 12 13 21 23 31 33 ,每個i都會經歷j <- 1 to 3 if i != j-
for推導式:循環體以yield開始,則循環會構造出一個集合,每次迭代生成集合中的一個值。且集合與它的第一個生成是類型兼容的。
- for( c <- "Hello";i<-0 to 1) yield (c+i).toChar
// String = "HIeflmlmop" - for( i <-0 to 1; c <-"Hello") yield (c+i).toChar
//Vector(H, e, l, l, o, I, f, m, m, p)
- for( c <- "Hello";i<-0 to 1) yield (c+i).toChar
-
沒有break語句,但是Breaks對象提供了break方法。
函數
方法對對象(不是類)進行操作,函數不是。在java中我們用靜態方法來模擬函數。scala.math中的sqrt就是函數。
定義函數:函數名、參數、函數體
def abs( x:Double ) = if( x >= 0 ) x else -x;
參數類型必須給出。
函數體有多個表達式,可以使用代碼塊。
返回類型:函數不是遞歸的就不需要指定返回類型,可以根據=符號右側表達推斷出來。
默認參數
定義: def decorate(str :String, left: String ="[", right: String ="]") = left + str +right
調用:decorate("hello") //返回"[hell0"變長參數:語法
def sum(args: Int* )={
var result = 0
for (arg <- args ) result += arg
result
}
- sum函數被調用時傳入的單個參數必須是整數,而不是一個整數區間,如果傳一個區間需追加:_*,告訴編譯器你希望這個參數被當做參數序列處理。
- val s = sum(1,4,3,3) 或 sum( 1 to 5:_*)//將1 to 5當做參數序列處理。
- 當調用變長參數且參數類型為Object的java方法,如MessageFormat的format方法,需要手工對基本類型進行轉換,如:
val ss = MessageFormat.format("the answer to {0} is{1}","everything",42.asInstanceOf[AnyRef]);
public static String format(String pattern, Object ... arguments)
過程
- 過程:如果函數體包含在花括號中單沒有前面的=號,那么返回類型就是Unit,這樣的函數稱作過程。過程沒有返回值。
def procedure(){
print("ddd");
}
或者
def procedure():Unit={
print("ddd");
}
懶值
- 當val被聲明為lazy時,他的初始化將被推遲,直到我們首次對他取值。
- 對于開銷較大的初始化語句十分有用
- 懶值介于val和def的中間狀態。
異常
- scala沒有受檢異常,不需要聲明函數或方法可能會拋出某種異常。
- throw表達式的返回類型是Nothing。
- 對于if/else,如果有一個分支是Nothing,那么if/else表達式類型就是另一個分支。
- if( x >= 0 ) { sqrt(x) } else throw new IllegalArgumentException("x should not be negative" ) 類型為Double
-
捕獲異常采用模式匹配。
捕獲異常 - 如果不使用異常則可用_來代替變量名
- try/finally可以釋放資源
快學scala第三章習題答案
3數組相關操作
- 長度固定使用Array,長度變化用ArrayBuffer。用toBuffer、toArray方法相互轉化。
- val nums = new ArrayInt //數組初始化為0
- val b = ArrayBufferInt
- b +=1 , b+=(1,2,5),b++= Array(1,2,3,8)
- 提供初始值是不要用new
- val s = Array("hello","world")
- 用()來訪問元素
- nums(2)
- 用for( elem <- arr )來變量元素
- 用for( ele<-arr if ...) ...yield ...來將原數組轉型為新數組,原數組不變。
- if( elem <- a if( elem % 2 == 0 ) yield 2 * elem //對每個偶數翻倍,并存在新集合中
- a.filter ( _ % 2 ==0 ).map(2 * _) 另一個寫法
- scala數組和java數據可以互操作,用ArrayBuff,使用scala.collection.javaConversions中的轉函數。
常用算法
- Array(1,2,3).sum 求和,元素是數值類型
- Array("dd","a","zh").max、min //求最大最小元素
- Array("dd","a","zh").sorted返回排序的數組或緩沖數組,原數組不變
- scala.util.Sorting.quickSort(a) :對數組排序,但不適用緩沖數組
- min、max、quickSort操作,元素必須支持比較操作,包括數字、字符串、及帶Ordered特質的類型
- mkString:顯示數組或緩沖數組內容,允許指定元素間的分隔符
解讀Scaladoc
-
對Array類的操作方法列在ArrayOps相關條目下,在數組上應用這些操作之前,數組都會被轉化成ArrayOps對象。
數組函數解讀
數組函數解讀
二維數組
- Double的二維數組類型為:Array[Array[Double]]。
- 構造方法:val matrix = Array.ofDim[Double](3,4) //三行,四列
- 訪問元素:matrix(row)(column)
與java的互操作
- scala數組是由java數組實現的,你可以在java和scala之間來回傳遞。
- 引入import scala.collection.JavaConversions.bufferAsJavaList 可以實現Scala到Java的轉換
import scala.collection.JavaConversions.bufferAsJavaList
val command = ArrayBuffer("ls","-al");
val pb = new ProcessBuilder(command);
其中ProcessBuilder為java中的類, 構造函數為public ProcessBuilder(List<String> command)
- 引入import scala.collection.JavaConversions.asScalaBuffer 可以實現Java集合到Scala的轉換
import scala.collection.JavaConversions.asScalaBuffer
val pb = new ProcessBuilder(command);
val cmd:Buffer[String] = pb.command();
其中ProcessBuilder為java中的類, public List<String> command()
4映射和元組
- 不可變映射和可變映射
- 默認情況下得到的是一個哈希映射,不過可以指明要樹形映射
創建映射
- val scores = scala.collection.immutable.Map("a"->10,"b"->5) //不可變的Map[String,Int],其值不可變
- val s = scala.collection.mutable.Map("a"->20) //可變映射
- val d = new scala.collection.mutable.HashMap[String,Int] //空映射
- 映射是對偶的集合
- ->操作符和(key,value)來創建對偶 : "dd"->2 或者 ("dd",2)
獲取映射值
- 使用()符號,s("a")
-
如果映射不存在請求使用的健,會拋出異常
- 檢測映射中是否包含鍵:contains方法
- s.getOrElse("b",0) //如果不存在b鍵則返回0.
更新映射值
- 針對可變映射
- s("a") = 6 //更新a鍵的值位6,或者是增加鍵值對"a"->6
- s+= ("w"->3,"ss"->9) //增加多個
- s-= "a" //移除鍵a
迭代映射
- 語法:
for( (k,v)<- 映射 ) 處理k和v
-
for( v <- s.values ) 處理v
//只訪問值,鍵用keySet -
for( (k,v) <- 映射 ) yield (v,k)
//交換鍵和值得位置
已排序映射
- 默認情況下映射的實現是一個哈希映射,不過可以指明要樹形映射。
- 輸出的內容是有序的而不是根據輸入順序,按照字典序排序
- 樹形映射同樣有可變和不可變的。
var s = collection.mutable.SortedMap("bc"->5) //可變映射,可變變量s
s("b")=3 // b->3 ,bc- > 5
s+=("a"->2) //a->2 , b->3 ,bc- > 5
與java互操作
- 之前是引入scala
- 引入import scala.collection.JavaConverters._對象
val source = new java.util.HashMap[String,Int]
val s:scala.collection.mutable.Map[String,Int] = source.asScala
val source = new scala.collection.mutable.ListBuffer[Int]
val target: java.util.List[Int] = source.asJava
val other: scala.collection.mutable.Buffer[Int] = target.asScala
assert(source eq other)
- 過時方法:
import scala.collection.JavaConversions.mapAsScalaMap
import scala.collection.JavaConversions.propertiesAsScalaMap val
val map:Map[String,Int] = new TreeMap[String,Int]
props:scala.collection.Map[String,String] = System.getProperties()
元組(tuple)
- 不同類型值的聚集
- 如:val s =(1, 2.3, "ddd") 對應類型為:Tuple3[Int,Double,java.lang.String]或(Int,Double,java.lang.String)
- 獲取元組值
- _序號 訪問組元,元組的各組元從1開始,而不是0
- 如: s._2 或 s _2(空格) //2.3
- 模式匹配獲取
- val (first,second,_) = s // 在不需要的位置上用_,
拉鏈操作zip
-
把key和value組合在一起
拉鏈操作
"Hello".zip("World")
res0: scala.collection.immutable.IndexedSeq[(Char, Char)] = Vector((H,W), (e,o), (l,r), (l,l), (o,d))
5類
- 一個主構造器,多個輔助構造器,輔助構造器叫做this
getter和setter屬性
- 類中字段自動帶有getter、setter方法,并且對應的getter和setter方法為:age和age_=。(假設屬性為age)。屬性getter、setter方法的公有和私有性與字段有關。
- 可通過javap -c 字節碼文件:查看生成的信息。
class Person(val name:String,var sex:Boolean){ //公有name的get,公有sex的get、set方法
var age =0 //公有字段,公有的getter、setter方法
private var address="china" //私有字段,私有的getter、setter方法
val school="cnu" //只getter方法
private[this] var tel="1134423" //對象私有字段,不會生產getter和setter方法
}
- 定制getter、setter方法
class Person(){
private var myAge =0 //公有字段,公有的age、age_=方法
def age = myAge //為myAge定制get、set方法
def age_=(newAge:Int){
if( newValue > myAge ) myAge = newAge
}
}
Bean屬性
- JavaBeans規范把java屬性定義為一對getFoo/setFoo方法(或者對只讀屬性而言單個getFoo方法),而scala提供的getter、setter方法的名稱并不是java所預期的。但是許多java工作都依賴javaBeans的名稱習慣,所以通過將字段標注為@BeanProperty時,就會生產javaBeans版的getter、setter方法。
輔助構造器
- 名稱為this
- 每一個輔助構造器必須以一個對先前已定義的其它輔助構造器或主構造器的調用開始。
主構造器
-
主構造器參數直接放置在類名之后
image.png -
主構造器參數生成字段和方法
主構造器 注意:class Person(name :String){}//對于這種name沒有被方法使用的情況,Person不會生成該字段。
嵌套類
- Java中的內部類從屬于外部類。但是Scala中,每個實例都有自己的內部類,就像自己的字段一樣。
class Person{
private val members = new ArrayBuffer[Member]
class Member(val name:String){
}
}
val zhangsan = new Person
val lisi = new Person
- 其中zhansan.Member和lisi.Member是兩個不同的類。
- 如果不希望這種效果:(即zhansan.Member和lisi.Member是兩個不同的類。),有2種方式解決:
- 1.將Member類移到別處:推薦位置是伴生對象,即Person的伴生對象中。
object Person{
class Member(val name:String){
}
}
class Person{
private val members = new ArrayBuffer[Person.Member] //注意
}
- 2.類型投影:Person#Member, 含義:“任何Person的Member”
- 內嵌類中,可以通過 外部類.this的方式來訪問外部類的this引用。
- 也可以使用class Person { outer=>語法來使outer變量指向Person.this。
快學scala第五章習題答案
6對象
用對象作為單例或存放工具方法
object Accounts{
private var lastNumber =0
def newUniqueNumber() ={lastNumber += 1 ;lastNumber }
}
- scala沒有靜態方法和字段,用object來達到同樣目的,對象定義了某個類的單個實例。對象的構造器在該對象唄第一次使用時調用,如果一個對象從未被使用,那么構造器不會執行。
類可以擁有一個同名的伴生對象(在同一個源文件中)
- java中經常會用到既有實例方法又有靜態方法的類,在Scala中通過類與同名的“伴生”對象來達到同樣目的。
class Accounts{
val id = Accounts.newUniqueNumber()
private var balance =0.0
def deposit(amount: Double){balance += amount}
}
object Accounts{
private var lastNumber =0
def newUniqueNumber() ={lastNumber += 1 ;lastNumber }
}
- 類和它的伴生對象可以相互訪問私有特性,他們必須存在同一個源文件中
對象可以擴展類或特質
- object對象可擴展類及多個特質
- image.png
- val acions = Map("open"->DoNothingAction, "save"-> DoNothingAction)
對象的apply方法通常用來構造伴生類的新實例
- Object(參數1,...,參數N) 表達式就會調用apply方法。
- 因為Account定義了apply方法,val acct = Account(1000.0)就變得可行了。
如果不想顯示定義main方法,可以擴展App特質的對象
你可以通過擴展Enumeration對象來實現枚舉
7.包和引入
包
- 同一個包可以定義在多個文件當中,源文件目錄和包也沒有強制關聯關系:Utils.scala可以不在com/horstmann目錄下。
package com {
package horstmann{
class Utils{}
pacakge impatient{
class Employee{ } //作用域:可以訪問Utils類中方法
}
}
}
等同于
package com.horstmann
class Utils{}
package impatient
class Employee{}
串聯式包語句
- 這樣的包語句限定了可見成員,com.horstmann、com的成員在這里不在可見
package com.horstmann.impatient{
package people{
class Person
}
}
包對象
package com.horstmann.impatient
package object people{
val defaultName = "John Public"
}
package people{
class Man{
val name = defaultName //從包對象拿到常量值,因為在同一個包中不需要使用com....people.defaultName訪問
}
}
- 可以把工具函數或常量添加到包而不是某個Utils對象。
- 每個包都可以有一個包對象,你需要在父包中定義它,且名稱與子包一樣。
- 使用:不在同一個包中使用 com.horstmann.impatient.people.defaultName訪問
包可見性
-
Java中被default(默認)修飾的成員在包含該類的包中是可見的。Scala中可以通過修飾符可以使類中成員在包中可見。
類成員可見度
類私有字段、對象私有字段
- scala中方法默認可以訪問該類的所有對象的私有字段。
class Counter{
private var value =0 //對象私有字段,Counter類方法可以訪問Counter類所有對象的value字段
private[this] var age =0 //對象私有字段,Counter類方法只能訪問到當前對象的age字段
def increment() = {value+=1}
//可以訪問other.value字段,因為other也是Counter類。
def isLess(other:Counter ) = value < other.value
//下面other.age訪問是錯誤的,因為age是對象私有的
def isLessAge(other:Counter ) = age < other.age
}
引入
- 重命名和隱藏
import java.util.{HashMap => JavaHashMap} //重命名
import java.util.{HashMap =>_,_} //隱藏,第二個“_”符號是java.util._的意思
- 隱式引入
- 但是scala下的類會覆蓋java.lang下的類,而不是報錯
import java.lang._
import scala._
import Predef._
- 在任何地方都可以聲明引入,import的效果一直延伸到包含該語句塊的末尾。
class Manager{
import scala.collection.mutable._ //該引入只在Manager{}語句塊中有用
val sub = new ArrayBuffer[Employee]
}
8繼承
- extends、final關鍵字和java中相同
- 類、字段、方法聲明為final就不能被繼承和重寫。(而java中字段final只是表明不可變,可以被重寫)
- 重寫方法、字段時必須用override,重寫超類抽象方法則不需要
- 調用超類方法同樣用super。eg:super.toString()
- 只有主構造器可以調用超類的主構造器
class Employee(name:String,age:Int,val salary:Double) extends Person(name,age)
- 與java不同protected的成員對于類所屬包是不可見的,可以通過protected[包]實現
類型檢查和轉換
Scala | Java | 說明 |
---|---|---|
obj.isInstanceOf[CI] | obj instanceof CI | obj是否屬于CI類及其子類 |
obj.asInstanceOf[CI] | (CI)obj | 將obj轉換成CI類,如果obj是CI子類會出錯 |
obj.getClass == classOf[CI] | CI.class | 測試obj是否是CI類,但又不是其子類 |
抽象類
- abstract關鍵字,抽象方法省去其方法體,存在至少一個抽象方法,則必須聲明為abstract
- 重寫超類抽象方法則不需要override
構造順序和提前定義
- 當你在子類中重寫val并且在超類的構造器中使用該值的話,會出現不符合預期的結果。(和java一樣)
- 舉例:下面的Ant類繼承Creature,并且重寫range字段,getter方法也被重寫。在實例化Ant之前會先實例化父類Creature,在執行Createture構造函數中,先設置range字段為10 ,然后為了初始化env數組,用到了range的getter方法,getter方法被重寫,所以調用子類getter方法,此時Ant類還未進行實例化,range字段的值為零值,所以返回0,所以env的數組長度為0.
class Creature{
val range:Int = 10
val env:Array[Int] = new Array[Int](range)
}
class Ant extends Creature{
override val range = 2
}
//下面代碼寫在main函數中
val ant = new Ant
print(ant.env.length) //返回0
- 解決上面問題明白2個原理:1.類加載中,父類先與子類之前執行初始化動作。類加載的初始化階段會執行<cinit>函數,即對類變量(static修飾的)進行賦值。2.父類的的實例化先于子類實例化,非類變量在實例化中會被賦值。
- 解決方法:
- 將超類val聲明為final,則在scala中該字段就不能被重寫(注:java中可以),將子類重寫的字段聲明為final可以得到正確的值,對于本例就是2
- 將超類的env聲明為lazy,即env在使用時才初始化,而此時ant早已實例化完成。
- 在子類使用提前定義語法:讓你可以在超類的構造器執行之前初始化子類的val字段。
- class Ant extends { override val range = 2 } with Creature
Scala繼承層級
對象相等性
- AnyRef的eq方法檢查兩個引用是否指向同一個對象,AnyRef的equals調用eq。
- 重寫equals,提供一個自然的與實際相稱的相等性判斷,語法定義:加上final,參數類型必須為Any.
- final override def equals(other: Any)={}
- 在程序中,只要用==就可以,對于應用類型而言,他會在做完必要的null檢查后調用equals方法。