標籤(空格分隔): scalajava
屬性的聲明:
scala類的屬性有4種方法定義:var
, val
, private var
, 同類對象私有字段private[this] var
python
class Person{ var varage = 0 // 類中全部var產生公有的setter和getter val valage = -1 // 類中全部val產生公有的getter private var age1 = 0 // 產生私有的setter和getter private[this] var age2 = 3 // 不產生setter,getter // 經過複寫setter和getter def age = age2 // getter def age_=(newAge:Int) = { // setter : field_= this.age2 = newAge } }
scala的setter和getter調用es6
val p = new Person p.varage_=(2) // setter p.age // getter
主副構造器
(1)若是一個類沒有顯示的聲明主構造器,則會自動加入無參構造
(2)輔助構造器名稱爲this(避免修改類名時要修改多個輔助構造器的名稱)'
(3)輔助構造器的開頭必須以主構造器開始
(4)主構造器中的字段會自動被解析成類中的屬性api
class Person(var name:String,var age:Int){ def this(name:String){ this(name,-1) } def update(name:String,age:Int) = { print("update function is called") this.age = age } }
伴生對象
(1)伴生對象適用於既有實例方法,又有靜態方法的時候
(2)伴生對象中的apply方法能夠用來不帶new產生對象,apply的方法體要調用伴生類的主/輔助構造器方法
(3)伴生對象中的unapply方法,能夠在模式匹配中用於屬性匹配
```scala
object Person{
def apply(name: String): Person = new Person(name)
def unapply(arg: Person): Option[Int] = Option(arg.age)
}緩存
// 繼承的寫法
class Student(name:String,age:Int,val sid:String) extends Person(name){
}網絡
object Main extends App{
val p = Person("lj") // 利用伴生對象的apply方法產生對象
p match { // 模式匹配至關於手動調用了下面的unapply方法
case Person(-1) => println("match success")
}
if (Person.unapply(p).get == -1)
println("unapply match success")app
val s1 = new Student("lj",26,"09101306")ide
p("lj") = 27 // update function
}
```函數
聲明枚舉類型
(1)object繼承Enumeration
(2)枚舉的屬性調用Value方法
(3)枚舉的name自動設置爲屬性名
```scala
object Color extends Enumeration{
val Red = Value
val Yellow = Value
}this
object Test1 extends App{
println(Color.Yellow.toString) // Yellow
println(Color.Yellow.id) // 1
}
```
def getOptval(aaa:Person):Option[Person] = Some(aaa) print(getOptval(new Person(1,2)).get) //Person@7921b0a2
(1)全部集合繼承自Iterable特質,所以,訪問全部集合的通用代碼爲:
val coll = ... // 某種集合 val iter = coll.iterator while(iter.hasnext) iter.next
(2)scala的集合大體分爲3類:
i) Seq:按照插入順序排序的序列
ii) Set:每次插入一個元素,都會根據某種經排序方法決定元素在集合中所處的位置。set中沒有重複的元素
iii) Map:鍵值對對偶
不可變序列:
(1)Vector是ArrayBuffer的不可變版本,它擁有下標,以樹型結構存儲節點,支持快速的隨機訪問。每一個節點最多可存放32個子節點。所以,對於一個100萬個元素的向量,只須要四層節點(\(10^6 \approx32^4\)),訪問任意一個元素,最多隻須要4眺
(2)Range是一個整數序列,好比1,2,3,4,5,6,7,8,9 它不存儲全部元素,只存儲起始值,結束值和增值
可變序列:
scala中的列表要麼是Nil(空表),要麼是一個head元素加上一個tail(列表)。如下列表的聲明等價
```scala
scala> 9::4::2::Nil
res5: List[Int] = List(9, 4, 2)
scala> List(9,4,2)
res6: List[Int] = List(9, 4, 2)
scala> 9::List(4,2)
res7: List[Int] = List(9, 4, 2)
集合操做
(1)向後追加元素(:+),向前追加元素(+:) (只用於向插入順序
有關的集合中追加)
scala> val v1 = Vector(1,2,3,4) scala> v1 :+ 8 res27: scala.collection.immutable.Vector[Int] = Vector(1, 2, 3, 4, 8) scala> 9 +: v1 res28: scala.collection.immutable.Vector[Int] = Vector(9, 1, 2, 3, 4)
(2)+
操做符,向Set等與插入順序無關的集合中追加元素
scala scala> val s1 = Set(1,2,3,4) scala> s1 +9 res31: scala.collection.immutable.Set[Int] = Set(1, 9, 2, 3, 4)
(3)++
與--
分別爲向集合中追加多個元素
reduceLeft
和reduceRight
(1)coll.reduceLeft(op)表達式,將op函數相繼應用到集合中的元素,如圖造成一個樹形結構
(2)reduceLeft是從集合的左端開始,reduceRight是從集合的右端開始
scala> List(1,7,2,9).reduceLeft(_-_) // 1-7-2-9 scala> List(1,7,2,9).reduceRight(_-_) // 1-(7-(2-9)) res31: Int = -13
foldLeft
和foldRight
(1)摺疊方法讓調用者能夠自定義集合計算的初始元素,進行樹型結構的計算
scala scala> List(1,7,2,9).foldRight(5)(_-_) // 1-(7-(2-(9-5))) = -8 scala> List(1,7,2,9).foldLeft(5)(_-_) // 5-1-7-2-9 res33: Int = -14
(2)foldLeft
和foldRight
的簡寫形式:/:
和:\
scala> (5 /: List(1,2,3))(_-_) // 5-1-2-3 res35: Int = -1(3)利用摺疊方法計算詞頻
scala scala> (Map[Char,Int]() /: "Mississippi"){(m,c) => m + (c -> (m.getOrElse(c,0)+1))} res43: scala.collection.mutable.Map[Char,Int] = Map(M -> 1, s -> 4, p -> 2, i -> 4)
scanLeft
和scanRight
將摺疊和映射組合在一塊兒,得出每一次計算的中間結果
scala> List(1,2,3,4).scanLeft(0)(_-_) res46: List[Int] = List(0, -1, -3, -6, -10)
定義:
(1)Stream是一個尾部被懶計算的不可變列表,經過操做符#::
能夠構造一個流
(2)Strea的尾部懶計算後會緩存起來
scala> val tenMore = numsFrom(10) tenMore: Stream[BigInt] = Stream(10, ?) scala> tenMore.tail.tail res51: scala.collection.immutable.Stream[BigInt] = Stream(12, ?) res52: Stream[BigInt] = Stream(10, 11, 12, ?) // 10,11,12已通過緩存
(3)用take得到多個答案,而後用force強制求值
scala scala> tenMore.take(5).force res54: scala.collection.immutable.Stream[BigInt] = Stream(10, 11, 12, 13, 14)
(4)不要不經take直接調用force,不然會一次計算出stream的全部尾值,知道內存溢出
全部的集合都能經過view方法轉換爲懶計算的視圖,視圖不一樣於流,連第一個元素也不去計算
scala> (0 to 5) res55: scala.collection.immutable.Range.Inclusive = Range(0, 1, 2, 3, 4, 5) scala> (0 to 5).view // 視圖 res56: scala.collection.SeqView[Int,scala.collection.immutable.IndexedSeq[Int]] = SeqView(...) scala> (0 to 5).view.map(_*2) // SeqView(....):全部元素全不計算 res57: scala.collection.SeqView[Int,Seq[_]] = SeqViewM(...) scala> (0 to 5).view.map(_*2).force res58: Seq[Int] = Vector(0, 2, 4, 6, 8, 10)
本章要點:
對於任意一個引用對象v,能夠獲得一個類型v.type。這個類型有兩個可能的值:v(對象自己,但以類型的形式出現) 和 null
對於那種返回this的方法,經過this.type把這些方法串接起來
(1)錯誤寫法
:Document類的setter方法最後返回了this,而該方法的返回值類型若是直接寫成Document,雖然能夠串聯調用setTile和setAuthor,可是一旦出現Document的子類Book,則Book產生的對象調用setatile後返回的類型被寫成Document,也就不能串聯調用setATitle和setAuthor
// 錯誤示例 class Document { def setTitle(title: String):Document = { println("set title:" + title) this } def setAuthor(author:String):Documente = { println("set author:" + author) this } } val doc = new Document() doc.setAuthor("lj").setTitle("scala Education") // setAuthor返回的Document類型,能夠串聯調用setTitle class Book extends Document{ def addChgapter(chapter:String) :Book = { println("add chapter:" + chapter) this } } val book = new Book() book.setTitle("another book").addChgapter("1 chapter") // 編譯報錯,由於setTitle返回的類型是Document,而Document類沒有addChapter方法
(2)正確寫法
:爲了使繼承Document的Book類的對象也能串聯調用,能夠改造這些setter方法的返回值爲this.type,這樣,Book類的對象book在調用setTitle方法時,返回的類型就是book.type,而因爲book對象有一個addChapter方法,所以能夠串接起來
class Document { def setTitle(title: String):this.type = { println("set title:" + title) this } def setAuthor(author:String):this.type = { println("set author:" + author) this } } class Book extends Document{ def addChgapter(chapter:String) :this.type = { println("add chapter:" + chapter) this } } val book = new Book() book.setTitle("another book").addChgapter("1 chapter")
(3)其次,若是想要定義一個接收object實例做爲參數的方法,也可使用單例類型。那麼爲何對於單例對象的方法不直接調用,還要傳進一個object對象做爲參數在調用呢?由於有人喜歡構造那種調用起來像是一句話的代碼
book set Title to "Scala for the impatient"
object Title class Document { private var useNextArgAs:Any = null // 用Title.type聲明傳入的是Title單例對象, 用this.type聲明返回值使得集成類的setter方法也能串聯調用 def set(obj:Title.type):this.type = { useNextArgAs = obj this } def to(arg:String) : Unit={ if (this.useNextArgAs==Title) println("set finish") else "" } } object Test extends App{ val doc = new Document() doc set Title to "scala for the impatient" // 構造英文語句 }
scala中,嵌套類屬於它包含的外部對象,即每一個實例都有本身的內部類
以下,chatter.member和myFace..member是不一樣的類。不能講任何一個網絡(NetWork)的成員(Member)加到另外一個網絡中
```scala
class NetWork{
class Member(val name:String) {
val contacts = new ArrayBuffer[Member] // 這個泛型Member,指的是[對象.Member]
}
private val members = new ArrayBuffer[Member]
def join(name:String) :Member= {
val m = new Member(name)
members += m
m
}
}
val chatter = new NetWork
val myFace = new NetWork
val Fred = chatter.join("Fred")
val Barney = myFace.join("Barney")
Fred.contacts += Barney
```
內部類從屬於每一個對象這種約束是默認存在的,若是不想要這種約束,應該把Member類挪到NetWork類的外面。更好的選擇是在Network的伴生對象中。若是想使用更爲鬆散的定義,能夠用類型投影
NetWork#Member,表示任何Network的Member
scala class NetWork{ class Member(val name:String) { val contacts = new ArrayBuffer[NetWork#Member] //val contacts = new ArrayBuffer[Member] // 這個泛型Member,指的是[對象.Member] } ... }
HashMap[String,(Int,Int)]
,可使用type關鍵字建立一個簡單別名,eg:index
。class Book{ import scala.collection.mutable._ type index = mutable.HashMap[String,(Int,Int)] }
結構類型是一組關於抽象方法,字段和類型的規格說明
,這些抽象方法,字段,類型是該規格類型必須具有的。
寫法上:用大括號包圍這些抽象方法,字段,類型
下例所示,appendlines方法的形參是任何具備append方法的對象和一個string泛型的iterater,appendlines會調用這個對象的append方法
def appendLines(target: {def append(str:String): Any},lines:Iterable[String]): Unit ={ for(l <- lines){ target.append(l);target.append("\n") } }
鴨子類型就像python這種動態類型語言,變量沒有類型,當你寫下obj.quack()時,運行時回去檢查obj指向的對象在那一刻是否具備quack方法。換句話說:你不須要把obj聲明爲Duck類型,只要它運行時有Duck的方法(走起來,叫起來像鴨子同樣)
符合類型的定義形式以下: \(T_1\) with \(T_2\) with \(T_3\) ...,表示要成爲該複合類型的實例,必須知足每個類型的要求(好比實現了這幾個特質的方法),所以,符合類型也稱做交集類型
val images = new ArrayBuffer[java.awt.Shape with java.io.Serializable] val rect = new Rectangle(5,10,20,30) // Rectangle extends Rectangle2D implements Shape,java.io.Serializable images += rect
new ArrayBuffer[java.awt.Shape with java.io.Serializable {def setBounds(x: Int, y: Int, width: Int, height: Int):Unit}]
表示這個ArrayBuffer裏的對象既要知足Shape和Serializable接口,還要存在setBounds方法
(1)scala提供了一種讓類型的描述趨於數學中置表達式形式的寫法:用中置表達式組合多個泛型。eg:用String Map Int
來表示Map[String,Int]
(2)寫法:泛型1 類 泛型2
(3)中置表達式也能夠用來模式匹配,eg:
case class Person[S,T](val name:S,val age:T) val p : String Person Int= Person("搖擺少年夢",19) p match { case "搖擺少年夢" Person 18=> println("matching is ok") case name Person age=> println("name:"+name+" age="+age) }
(1)scala的存在類型是爲了與java的類型統配符兼容
(2)寫法:在類型表達式後面跟上forSome{},裏面包含了type和val的聲明,這些聲明是對被forSome修飾的類型作一個限制。
下例中,t1的存在類型和t2的類型通配符是等價的,類型通配符是存在類型的語法糖
type t1 = Array[T] forSome { type T<:JComponent} type t2 = Array[_<:JComponent] // 存在類型容許使用更復雜的類型關係 type t3 = Map[T,U] forSome {type T,type U<:T}
有的嵌套類經過類型投影NetWork#Member
,擴大了泛型範圍。但一些方法又想把嵌套類侷限於每一個對象的嵌套類,就是用存在類型加以限定
val chatter = new NetWork val myFace = new NetWork val Fred = chatter.join("Fred") // 同一個網絡下的成員 val Fred2 = chatter.join("Fred2") // 同一個網絡下的成員 val Barney = myFace.join("Barney") // 不一樣網絡 Fred.contacts += Barney def process[M <: n.Member forSome { val n:NetWork }](m1:M,m2:M) = (m1,m2) process(Fred,Fred2) //process(Barney,Fred2) => 錯誤:process方法經過forSome裏面的val n:NetWork限制了n.Member爲對象自身的嵌套類,使得方法接收相同網絡的成員,拒毫不同網絡的成員
(1)自身類型是對特質自身的一種限制,它指出該特質只能被混入哪一個類中,或智能被混入哪一個類的子類中
(2)形式:this: 類型 =>
trait Logged{ def log(msg:String) } trait LoggerException extends Logged{ this:Exception => // 這個this指代混入特質後的對象 def log(){ log(getMessage()) // getMessage方法來自於this,而this又是Exception的子類 } } object Test extends App{ type f = JFrame with LoggerException // 定義類型時不報錯,建立對象時會由於LoggerException的自身類型限制而報錯 // val v1 = new f // 報錯:f類型是JFrame混入LoggerException,而JFrame不是Exception的子類 }
(1)若是自帶有自身類型限制的特質被另外一個特質集成,則子特質必須重複寫出自身類型,表示本身和父特質同樣也有混入的限制
trait ManagedException extends LoggerException{ this:ArrayIndexOutOfBoundsException => // 這裏的類型限制要定義成Exception或其子類 def say(){print("aaa")} }
(1)設想一個應用,他須要日誌和驗證功能,固然,驗證功能也須要用到日誌。
(2)設計:所以,能夠把日誌和驗證設爲2個特質Logger和Auth,這兩個特質分別有本身不一樣的實現。而真正的應用類App,只要混入這些不一樣的實現組合,就能使得App擁有日誌和驗證功能。而Auth須要用到Logger,因此在Auth特質中,經過自身類型調用Logger類型的方法
trait Logger{ def log(msg:String) } trait Auth{ this:Logger => // 自身類型調用Logger特質的方法 def login(id:String,passwd:String):Boolean } trait FileLogger extends Logger{ override def log(msg: String): Unit = { println(msg) } } trait MockAuth extends Auth{ this:FileLogger => override def login(id: String, passwd: String): Boolean = { if(id.equals("guest")) { log("guest login fail..") false }else true } } object App extends FileLogger with MockAuth{ // 此處經過依賴注入變換實現 def main(args: Array[String]): Unit = { login("guest","123456") } }
(3)這種方法的怪異之處在於,一個App並不是是驗證器和文件日誌器的組合。更天然地表述方式是使用成員變量來實現功能組件,而不是把App經過混入特質變成一個巨大的類型。
trait LogComponent{ // 最外層的大組件 trait Logger{ def log(msg:String) } val logger:Logger // 抽象變量 class FileLogger extends Logger{ override def log(msg: String): Unit = { println("write in file: "+msg) } } } trait AuthCompnonent{ // 最外層的大組件 this:LogComponent => // 使用抽象變量logger trait Auth{ def login(id:String,passwd:String):Boolean } val auth:Auth // 抽象變量 class MockAuth extends Auth{ override def login(id: String, passwd: String): Boolean = { if (id.equals("guest")){ logger.log("guest cannot login") // 這個logger變量來自於LoggerComponent,究竟是哪一個實現取決於繼承的特質 false }else true } } } object App extends LogComponent with AuthCompnonent{ override val logger = new FileLogger // 成員變量 override val auth = new MockAuth // 成員變量 def main(args: Array[String]): Unit = { auth.login("guest","123456") } }
(1)類或特質中,定義一個在子類中被具體化的抽象類型。eg:以下的Reader特質:
trait Reader{ type Contents def read(fileName:String):Contents } class StringReader extends Reader{ override type Contents = String override def read(fileName: String):Contents = Source.fromFile(fileName,"UTF-8").mkString // mkString method return string, corresponding with TYPE contents } class ImageReader extends Reader{ override type Contents = BufferedImage override def read(fileName: String): Contents = ImageIO.read(new File(fileName)) }
(2)固然,在須要子類給出抽象類型的實現這種方法,還能夠經過類型參數實現
trait Reader[C]{ def read(fileName:String):C } class StringReader extends Reader[String]{ override def read(fileName: String) = Source.fromFile(fileName,"UTF-8").mkString } class ImageReader extends Reader[BufferedImage]{ override def read(fileName: String) = ImageIO.read(new File(fileName)) }
(1)若是類型是在建立對象時給出時(不存在繼承該類的子類),就應當使用類型參數。好比構建HashMap[String,Int]
(2)若是類型是在子類中給出,則使用抽象類型。好比上面的Reader就是子類中給出。
(3)固然還有一種狀況是,在子類中給出類型參數。這種方法沒什麼很差,可是一旦父類或父特質中有多個類型參數,子類的定義就會變得冗長。eg:Reader[File,BufferedImage],這樣會使得伸縮性變差
trait Listener{ type Event <: java.util.EventObject } trait ActionListener extends Listener{ type Event = java.awt.ActiveEvent }
(1)隱士轉換:以implicit聲明的帶有單個參數的函數
(2)這個函數自動將一種類型轉換成另外一種類型
case class Fraction(a:Int,b:Int){ def *(second:Fraction) = Fraction(second.a*a,second.b*b) } object Test extends App{ implicit def int2Fraction(n:Int) = Fraction(n,1) val result = 3*Fraction(4,5) // Int沒有*(Fraction)的方法,但Fraction有*(Fraction)方法 println(result) }
(1)你多想用new File(README"").read
來讀取一個文件,可是jdk的File並未提供這個方法。做爲java,你只能向Oracle公司提交申請,可是scala卻能經過一年隱士轉換來豐富這個api
case class RichFile(filepath:String){ def read() = Source.fromFile(filepath).mkString("") } object Test extends App{ implicit def file2RichFile(from:File) = new RichFile(from.getAbsolutePath) new File("README").read() }
(1)當表達式所得值的類型,和所處位置的期待類型不同時
(2)當訪問一個不存在的成員或方法時。eg : File.read()
。也就是說當調用a.fun(b)時,若是存在2個隱士轉換使得:convert(a)的結果有fun方法,或者存在方法a.fun(convert(b)),則編譯器會使用convert(a)隱士轉換。由於編譯器會把沒有調用成員的對象隱士轉換
(1)若是在不進行隱士轉換的狀況下能夠經過編譯,則不進行隱士轉換
(2)變量只能通過一次隱士轉換,而不能進行形如convert1(convert2(a))這樣的屢次轉換
(3)若是兩個隱士轉換都知足條件,編譯器報錯。即:convert1(a)b與convert2(a)b都成立
隱士參數是函數的參數列表中,帶有implicit標記的形參。此時在調用該方法時,編譯器會查詢缺省值
case class Delimiters(left:String,right:String) object Test extends App{ def quote(what:String)(implicit delims:Delimiters) = delims.left + what + delims.right println(quote("impatient scala")(Delimiters("《","》"))) // 顯式調用 implicit val quoteDelimiters = Delimiters("'","'") // 隱士調用 println(quote("hello world")) }
(1)隱士轉換是一個隱士方法,當方法做爲參數傳入另外一個方法,就造成了含有隱士參數的高階方法
def smaller[T](a:T,b:T)(implicit order: T => Ordered[T]) = { if(order(a) < b) a else b } println(smaller("a","b"))
(2)隱士轉換做爲隱士參數的簡化聲明
def smaller2[T](a:T,b:T)(implicit order: T=>Ordered[T]) = { if(a<b) a else b // 隱士轉換自動執行,所以不用顯式調用 } println(smaller2(2,4))
(1)形如T:M
的泛型,表示程序的上下文中,存在一個類型爲M[T]
的隱式值。一般用於類的泛型限制
(2)類中的方法使用這個上下文界定的隱式值有兩種方法:
(i.) 經過定義implicit隱士形參
(ii) 在函數體中使用Predef
類的implicitly()方法,傳入上下文界定類型還原出這個隱式值
// method 1 class Pair[T:Ordering](val first:T,val second:T){ def smaller(implicit ord:Ordering[T])= if (ord.compare(first,second)<0) first else second } // method 2 class Pair1[T:Ordering](val first:T,val second:T){ def smaller = if( implicitly[Ordering[T]].compare(first,second) < 0 ) first else second } object Test1 extends App{ println(new Pair1(24,35).smaller) }
(3)用泛型定義的類,在實例化時,編譯器會經過成員變量推斷出泛型的類型。
(1)類型證實是形如implicit ev T <:< U
的一個隱士參數,其中<:<
還能夠是<=<
,<%<
.
(2)三個符號分別表示:T是不是U的子類型,T是否等於U,T是否能夠經過隱士轉換爲U
(1)<:<
,<=<
,<%<
3個符號並不是是語言特性,而是定義在Predef中的三個類
(2)舉例:<:<類的定義
下面的三行代碼,解釋了scala經過<:<類,實現類型證實的過程。
(i.) 首先:定義了一個帶有泛型的抽象類<:<
,這個類繼承的(From=>To),實際上就是一個Function1
(帶有一個形參的函數)
(ii) 其次:初始化了一個singleton_<:<
對象,這個對象的-From
和+To
泛型都是Any,並且複寫了apply方法爲傳入一個Any類型的參數x,而後把x返回出去
(iii)最後:定義了一個隱士轉換$conforms[A]
,它的返回值類型爲A <:< A
(<:<[A,A]
的中置寫法)。該方法就是將<:<[Any,Any]
強轉爲<:<[A,A]
(即:Function1[Any,Any]
轉換爲Function1[A,A]
)。而這個泛型A到底可否讓編譯器推斷出來,就是這個類型證實可否經過的關鍵
(v.)編譯器推斷:因爲<:<類的泛型一個逆變,一個協變。eg:對於<:<[String,AnyRef]
,編譯器就能推斷出A是String(String逆變成String,AnyRef協變成String)
@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.") sealed abstract class <:<[-From, +To] extends (From => To) with Serializable private[this] final val singleton_<:< = new <:<[Any,Any] { def apply(x: Any): Any = x } implicit def $conforms[A]: A <:< A = singleton_<:<.asInstanceOf[A <:< A]
def firstLast[T,IR](it:IR)(implicit env: IR<:<Iterable[T]) = (it.head,it.last) println(firstLast(List(1,2,3)))
(1)@implicitNotFound註解加載類上,該類須要時隱士轉換函數From->To
的To類。意義在於告知編譯器,再不能構建出這個To類時爆出錯誤信息
(2)例如:
@implicitNotFound(msg = "Cannot prove that ${From} <:< ${To}.") sealed abstract class <:<[-From, +To] extends (From => To) with Serializable