1. 程式人生 > >Scala基礎語法指南

Scala基礎語法指南

Scala

標籤(空格分隔): Scala
By Vinfly

Scala介紹

  • Scala與Java的關係
    Scala是基於Java虛擬機器,也就是JVM的一門程式語言。所有Scala的程式碼,都需要經過編譯為位元組碼,然後交由Java虛擬機器來執行。所以Scala和Java是可以無縫互操作的。Scala可以任意呼叫Java的程式碼。所以Scala與Java的關係是非常非常緊密的。
  • Scala安裝
    1、從Scala官方網站下載,http://www.scala-lang.org/download/,windows版本的安裝包是scala-2.11.7.msi。
    2、使用下載下來的安裝包安裝Scala。
    3、在PATH環境變數中,配置$SCALA_HOME/bin目錄。
    4、在windows命令列內即可直接鍵入scala,開啟scala命令列,進行scala程式設計。
  • Scala直譯器的使用
    1、 REPL:Read(取值)-> Evaluation(求值)->Print(列印)->Loop(迴圈)。scala直譯器也被稱為REPL,會快速編譯scala程式碼為位元組碼,然後交給JVM來執行。
    2、計算表示式:在scala>命令列內,鍵入scala程式碼,直譯器會直接返回結果給你。如果你沒有指定變數來存放這個值,那麼值預設的名稱為res,而且會顯示結果的資料型別,比如Int、Double、String等等。
    – 例如,輸入1 + 1,會看到res0: Int = 2
    3、內建變數:在後面可以繼續使用res這個變數,以及它存放的值。
    –例如,2.0 * res0,返回res1: Double = 4.0
    –例如,”Hi, ” + res0,返回res2: String = Hi, 2
    4、自動補全:在scala>命令列內,可以使用Tab鍵進行自動補全。
    –例如,輸入res2.to,敲擊Tab鍵,直譯器會顯示出以下選項,toCharArray,toLowerCase,toString,toUpperCase。因為此時無法判定你需要補全的是哪一個,因此會提供給你所有的選項。
    –例如,輸入res2.toU,敲擊Tab鍵,直接會給你補全為res2.toUpperCase。

Scala語法

宣告變數

  • 宣告val變數:可以宣告val變數來存放表示式的計算結果。
    –例如,val result = 1 + 1
    後續這些常量是可以繼續使用的
    –例如,2 * result
    但是常量聲明後,是無法改變它的值的,
    –例如,result = 1,會返回error: reassignment to val的錯誤資訊。
  • 宣告var變數:如果要宣告值可以改變的引用,可以使用var變數。
    –例如,val myresult = 1,myresult = 2
    但是在scala程式中,通常建議使用val,也就是常量,因此比如類似於spark的大型複雜系統中,需要大量的網路傳輸資料,如果使用var,可能會擔心值被錯誤的更改。
  • 在Java的大型複雜系統的設計和開發中,也使用了類似的特性,我們通常會將傳遞給其他模組 / 元件 / 服務的物件,設計成不可變類(Immutable Class)。在裡面也會使用java的常量定義,比如final,阻止變數的值被改變。從而提高系統的健壯性(robust,魯棒性),和安全性。
  • 指定型別:無論宣告val變數,還是宣告var變數,都可以手動指定其型別,如果不指定的話,scala會自動根據值,進行型別的推斷。
    –例如,val name: String = null
    –例如,val name: Any = “leo”
  • 宣告多個變數:可以將多個變數放在一起進行宣告。
    –例如,val name1, name2:String = null
    –例如,val num1, num2 = 100
    image_1ap0df831jvm1tt240917fu18fq9.png-14.8kB

資料型別與操作符

  • 基本資料型別
    Byte、Char、Short、Int、Long、Float、Double、Boolean。
    乍一看與Java的基本資料型別的包裝型別相同,但是scala沒有基本資料型別與包裝型別的概念,統一都是類。scala自己會負責基本資料型別和引用型別的轉換操作。使用以上型別,直接就可以呼叫大量的函式。
    –例如,1.toString(),1.to(10)。
  • 型別的加強版型別:scala使用很多加強類給資料型別增加了上百種增強的功能或函式。
    –例如,String類通過StringOps類增強了大量的函式,”Hello”.intersect(” World”)。
    image_1ap0e4991fpo124uftmie9ijm.png-11kB
    –例如,Scala還提供了RichInt、RichDouble、RichChar等型別,RichInt就提供了to函式,1.to(10),此處Int先隱式轉換為RichInt,然後再呼叫其to函式
    image_1ap0egu5u1tl079huqm12nr1ghl1g.png-21.7kB
  • 基本操作符:scala的算術操作符與java的算術操作符也沒有什麼區別,比如+、-、*、/、%等,以及&、|、^、>>、<<等。但是,在scala中,這些操作符其實是資料型別的函式,
    –比如1 + 1,可以寫做1.+(1) ,1.to(10),又可以寫做1 to 10
  • scala中沒有提供++、–操作符,我們只能使用+和-,比如counter=1,counter++是錯誤的,必須寫做counter += 1.

函式呼叫和apply()函式

  • ·函式呼叫方式:在scala中,函式呼叫也很簡單。

    ·例如,import scala.math._,sqrt(2),pow(2, 4),min(3, Pi)。
    ·不同的一點是,如果呼叫函式時,不需要傳遞引數,則scala允許呼叫函式時省略括號的,例如,”Hello World”.distinct

  • ·apply函式

    ·Scala中的apply函式是非常特殊的一種函式,在Scala的object中,可以宣告apply函式。而使用“類名()”的形式,其實就是“類名.apply()”的一種縮寫。通常使用這種方式來構造類的物件,而不是使用“new 類名()”的方式。
    ·例如,”Hello World”(6),因為在StringOps類中有def apply(n: Int):Char的函式定義,所以”Hello World”(6),實際上是”Hello World”.apply(6)的縮寫。
    image_1ap0f54jq1sse1k8ftv1ghifjn1t.png-8.3kB
    ·例如,Array(1, 2, 3, 4),實際上是用Array object的apply()函式來建立Array類的例項,也就是一個數組。
    image_1ap0fafss3qp1uau98a1qn68ri2q.png-12.2kB

條件控制和迴圈

  • if表示式
    if表示式的定義:在Scala中,if表示式是有值的,就是if或者else中最後一行語句返回的值。
    ·例如,val age = 30; if (age > 18) 1 else 0
    ·可以將if表示式賦予一個變數,例如,val isAdult = if (age > 18) 1 else 0
    ·另外一種寫法,var isAdult = -1; if(age > 18) isAdult = 1 else isAdult=0,但是通常使用上一種寫法
    ·if表示式的型別推斷:由於if表示式是有值的,而if和else子句的值型別可能不同,此時if表示式的值是什麼型別呢?Scala會自動進行推斷,取兩個型別的公共父型別。
    ·例如,if(age > 18) 1 else 0,表示式的型別是Int,因為1和0都是Int
    ·例如,if(age > 18) “adult” else 0,此時if和else的值分別是String和Int,則表示式的值是Any,Any是String和Int的公共父型別
    ·如果if後面沒有跟else,則預設else的值是Unit,也用()表示,類似於java中的void或者null。例如,val age = 12; if(age > 18) “adult”。此時就相當於if(age > 18) “adult” else ()。
    ·將if語句放在多行中:預設情況下,REPL只能解釋一行語句,但是if表示式通常需要放在多行。
    ·可以使用{}的方式,比如以下方式,或者使用:paste和ctrl+D的方式。
    if(age > 18) { “adult”
    } else if(age > 12) “teenager” else “children”
  • 語句終結符、塊表示式
    ·預設情況下,scala不需要語句終結符,預設將每一行作為一個語句
    ·一行放多條語句:如果一行要放多條語句,則必須使用語句終結符
    ·例如,使用分號作為語句終結符,var a, b, c = 0; if(a < 10) { b = b + 1; c = c + 1 }
    ·通常來說,對於多行語句,還是會使用花括號的方式
if(a < 10) {
     b = b + 1
     c = c + 1
} 

·塊表示式:塊表示式,指的就是{}中的值,其中可以包含多條語句,最後一個語句的值就是塊表示式的返回值。
·例如,var d = if(a < 10) { b = b + 1; c + 1 }
- 輸入和輸出
·print和println:print列印時不會加換行符,而println列印時會加一個換行符。
·例如,print(“Hello World”); println(“Hello World”)
·printf:printf可以用於進行格式化
·例如,printf(“Hi, my name is %s, I’m %d years old.\n”, “Leo”, 30)
image_1ap0gg420of51i4j1gk5s621d7037.png-20.3kB
readLine: readLine允許我們從控制檯讀取使用者輸入的資料,類似於java中的System.in和Scanner的作用
·綜合案例:遊戲廳門禁

val name = readLine("Welcome to Game House. Please tell me your name: ")
print("Thanks. Then please tell me your age: ")
val age = readInt()
if(age > 18) {
  printf("Hi, %s, you are %d years old, so you are legel to come here!", name, age)
} else {
  printf("Sorry, boy, %s, you are only %d years old. you are illegal to come here!", name, age)
} 
  • 迴圈
    ·while do迴圈:Scala有while do迴圈,基本語義與Java相同。
var n = 10
while(n > 0) {
  println(n)
  n -= 1
}

·Scala沒有for迴圈,只能使用while替代for迴圈,或者使用簡易版的for語句
·簡易版for語句:var n = 10; for(i <- 1 to n) println(i)
·或者使用until,表式不達到上限:for(i <- 1 until n) println(i)
·也可以對字串進行遍歷,類似於java的增強for迴圈,for(c <- “Hello World”) print(c)
·跳出迴圈語句
·scala沒有提供類似於java的break語句。
·但是可以使用boolean型別變數、return或者Breaks的break函式來替代使用。

import scala.util.control.Breaks._
breakable {
    var n = 10
    for(c <- "Hello World") {
        if(n == 5) break;
        print(c)
        n -= 1
    }
}

image_1ap0htjbi1n8r1lda1jied2cuba3k.png-23.1kB

  • 高階for迴圈
    ·多重for迴圈:九九乘法表
for(i <- 1 to 9; j <- 1 to 9) {
  if(j == 9) {
    println(i * j)
  } else {
    print(i * j + " ")
  }
}

·if守衛:取偶數
for(i <- 1 to 100 if i % 2 == 0) println(i)

·for推導式:構造集合
for(i <- 1 to 10) yield i
image_1ap0i7tjidn9n0alr61kck17d241.png-24.4kB

函式

  • 函式的定義和呼叫
    在Scala中定義函式時,需要定義函式的函式名、引數、函式體。
def sayHello(name: String, age: Int) = {
  if (age > 18) { printf("hi %s, you are a big boy\n", name); age } 
  else { printf("hi %s, you are a little boy\n", name); age 
}

呼叫:sayHello(“leo”, 30)
Scala要求必須給出所有引數的型別,但是不一定給出函式返回值的型別,只要右側的函式體中不包含遞迴的語句,Scala就可以自己根據右側的表示式推斷出返回型別。

  • 在程式碼塊中定義包含多行語句的函式體
    單行的函式:def sayHello(name: String) = print(“Hello, ” + name)

如果函式體中有多行程式碼,則可以使用程式碼塊的方式包裹多行程式碼,程式碼塊中最後一行的返回值就是整個函式的返回值。與Java中不同,不是使用return返回值的。

比如如下的函式,實現累加的功能:

def sum(n: Int) = {
  var sum = 0;
  for(i <- 1 to n) sum += i
  sum
}
  • 遞迴函式與返回型別
    如果在函式體內遞迴呼叫函式自身,則必須手動給出函式的返回型別。

例如,實現經典的斐波那契數列:
9 + 8; 8 + 7 + 7 + 6; 7 + 6 + 6 + 5 + 6 + 5 + 5 + 4; ….

def fab(n: Int): Int = {
if(n <= 1) 1
else fab(n - 1) + fab(n - 2)
}

  • 預設引數和帶名引數
    預設引數:
    在Scala中,有時我們呼叫某些函式時,不希望給出引數的具體值,而希望使用引數自身預設的值,此時就定義在定義函式時使用預設引數。

def sayHello(firstName: String, middleName: String = "William", lastName: String = "Croft") = firstName + " " + middleName + " " + lastName

如果給出的引數不夠,則會從作往右依次應用引數。
帶名引數:
在呼叫函式時,也可以不按照函式定義的引數順序來傳遞引數,而是使用帶名引數的方式來傳遞。

sayHello(firstName = "Mick", lastName = "Nina", middleName = "Jack")

還可以混合使用未命名引數和帶名引數,但是未命名引數必須排在帶名引數前面。

sayHello("Mick", lastName = "Nina", middleName = "Jack")

  • 變長引數
    在Scala中,有時我們需要將函式定義為引數個數可變的形式,則此時可以使用變長引數定義函式。
def sum(nums: Int*) = {
  var res = 0
  for (num <- nums) res += num
  res
}

image_1ap0mv6qui851qme14o817g811ih4e.png-31.6kB
- 使用序列呼叫變長引數

在如果想要將一個已有的序列直接呼叫變長引數函式,是不對的。
比如val s = sum(1 to 5)
image_1ap0n9eug11jk10ni1mr6152718qb9.png-30.6kB
此時需要使用Scala特殊的語法將引數定義為序列,讓Scala直譯器能夠識別。這種語法非常有用!
val s = sum(1 to 5: _*)
image_1ap0ndq621njk1ai61kdl1i4u18ckm.png-10.1kB
案例:使用遞迴函式實現累加

def sum2(nums: Int*): Int = {
  if (nums.length == 0) 0
  else nums.head + sum2(nums.tail: _*)
}
  • 過程
    在Scala中,定義函式時,如果函式體直接包裹在了花括號裡面,而沒有使用=連線,則函式的返回值型別就是Unit。這樣的函式就被稱之為過程。過程通常用於不需要返回值的函式。
    過程還有一種寫法,就是將函式的返回值型別定義為Unit。
    def sayHello(name: String) { print("Hello, " + name); "Hello, " + name }
    def sayHello1(name: String): Unit = "Hello, " + name
  • Lazy值
    在Scala中,提供了lazy值的特性,也就是說,如果將一個變數宣告為lazy,則只有在第一次使用該變數時,變數對應的表示式才會發生計算。這種特性對於特別耗時的計算操作特別有用,比如開啟檔案進行IO,進行網路IO等。
    import scala.io.Source._
    lazy val lines = fromFile("C://Users//Administrator//Desktop//test.txt").mkString

    即使檔案不存在,也不會報錯,只有第一個使用變數時會報錯,證明了表示式計算的lazy特性。

陣列

  • Array與ArrayBuffer
    在Scala中,Array代表的含義與Java中類似,也是長度不可改變的陣列。此外,由於Scala與Java都是執行在JVM中,雙方可以互相呼叫,因此Scala陣列的底層實際上是Java陣列。例如字串陣列在底層就是Java的String[],整數陣列在底層就是Java的Int[]。

// 陣列初始化後,長度就固定下來了,而且元素全部根據其型別初始化
image_1ap0p5dkk57q133u7in1ug81rhl13.png-23.4kB
// 可以直接使用Array()建立陣列,元素型別自動推斷
image_1ap0p72fe1c461jmt1hvj1mu0qfg1g.png-34.2kB

在Scala中,如果需要類似於Java中的ArrayList這種長度可變的集合類,則可以使用ArrayBuffer。
// 如果不想每次都使用全限定名,則可以預先匯入ArrayBuffer類
import scala.collection.mutable.ArrayBuffer
// 使用ArrayBuffer()的方式可以建立一個空的ArrayBuffer
val b = ArrayBuffer[Int]()
image_1ap0pjetnk2d18kt1osc10aheum1t.png-19.6kB
// 使用+=操作符,可以新增一個元素,或者多個元素
b += 1
b += (2, 3, 4, 5)
image_1ap0pm3ns10jkcg71376v4m1ud42a.png-9.3kB
image_1ap0pmgkr1gfq1mom18pv16qo1v5o2n.png-13.3kB
// 使用++=操作符,可以新增其他集合中的所有元素
b ++= Array(6, 7, 8, 9, 10)
image_1ap0ppli81fvgm3bt681ti2gn934.png-33.3kB
// 使用trimEnd()函式,可以從尾部截斷指定個數的元素
b.trimEnd(5)
image_1ap0pqoo2107f1sedjt16e9nu93h.png-27.3kB
// 使用insert()函式可以在指定位置插入元素
// 但是這種操作效率很低,因為需要移動指定位置後的所有元素
b.insert(5, 6)
b.insert(6, 7, 8, 9, 10)

// 使用remove()函式可以移除指定位置的元素
b.remove(1)
b.remove(1, 3)

// Array與ArrayBuffer可以互相進行轉換
b.toArray
a.toBuffer

  • 遍歷Array和ArrayBuffer
    使用for迴圈和until遍歷Array / ArrayBuffer
    // until是RichInt提供的函式
    x
    for (i <- 0 until b.length)
    println(b(i))

    // 跳躍遍歷Array / ArrayBuffer
    for(i <- 0 until (b.length, 2))
    println(b(i))

    image_1ap4iv0a0iqt94315o614ku18ut9.png-13.4kB
    // 從尾部遍歷Array / ArrayBuffer
    for(i <- (0 until b.length).reverse)
    println(b(i))

    // 使用“增強for迴圈”遍歷Array / ArrayBuffer
    for (e <- b)
    println(e)
    image_1ap4j34e61csl4dr1se11shr1g29m.png-10.1kB
  • 陣列常見操作
    // 陣列元素求和
    val a = Array(1, 2, 3, 4, 5)
    val sum = a.sum
    // 獲取陣列最大值
    val max = a.max
    image_1ap4jmdb1iod29q1psirq41raq13.png-37.5kB
    // 對陣列進行排序
    scala.util.Sorting.quickSort(a)
    執行完後a會按照字典序進行排序
    // 獲取陣列中所有元素內容
    a.mkString
    a.mkString(“, “)
    a.mkString(“<”, “,”, “>”)
    // toString函式
    a.toString
    b.toString

陣列轉換

  • 使用yield和函數語言程式設計轉換陣列
    // 對Array進行轉換,獲取的還是Array
    val a = Array(1, 2, 3, 4, 5)
    val a2 = for (ele <- a) yield ele * ele

    image_1ap4k0k6p1ien1k55gmr1lc05ei1g.png-30.9kB
    // 對ArrayBuffer進行轉換,獲取的還是ArrayBuffer
    val b = ArrayBuffer[Int]()
    b += (1, 2, 3, 4, 5)
    val b2 = for (ele <- b) yield ele * ele

    // 結合if守衛,僅轉換需要的元素
    val a3 = for (ele <-a if ele % 2 == 0) yield ele * ele
    image_1ap4kbrht1snm35h776kii1kpe1t.png-29.9kB
    // 使用函數語言程式設計轉換陣列(通常使用第一種方式)
    a.filter(_ % 2 == 0).map(2 * _)
    a.filter { _ % 2 == 0 } map { 2 * _ }

    image_1ap4keu4svmtrb01a4lj0s1k6m2a.png-14.3kB
    演算法案例:移除第一個負數之後的所有負數
// 構建陣列
val a = ArrayBuffer[Int]()
a += (1, 2, 3, 4, 5, -1, -3, -5, -9)
// 每發現一個第一個負數之後的負數,就進行移除,效能較差,多次移動陣列
var foundFirstNegative = false
var arrayLength = a.length
var index = 0
while (index < arrayLength) {
  if (a(index) >= 0) {
    index += 1
  } else {
    if (!foundFirstNegative) { foundFirstNegative = true; index += 1 }
    else { a.remove(index); arrayLength -= 1 }
  }
} 

改良版

// 每記錄所有不需要移除的元素的索引,稍後一次性移除所有需要移除的元素
// 效能較高,陣列內的元素遷移只要執行一次即可
var foundFirstNegative = false
val keepIndexes = for (i <- 0 until a.length if !foundFirstNegative || a(i) >= 0) yield {
  if (a(i) < 0) foundFirstNegative = true
  i
}
//結果1
for (i <- 0 until keepIndexes.length) { a(i) = a(keepIndexes(i)) }
a.trimEnd(a.length - keepIndexes.length)
//結果2

結果1:輸出的是不需要移除的陣列元素的索引
image_1ap51d2sdhlicokvgbe661she34.png-23kB

Map與Tuple

  • Map

建立Map

// 建立一個不可變的Map
val ages = Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23)
ages("Leo") = 31
// 建立一個可變的Map
val ages = scala.collection.mutable.Map("Leo" -> 30, "Jen" -> 25, "Jack" -> 23)
ages("Leo") = 31

// 使用另外一種方式定義Map元素
val ages = Map(("Leo", 30), ("Jen", 25), ("Jack", 23))
// 建立一個空的HashMap
val ages = new scala.collection.mutable.HashMap[String, Int]

訪問Map

// 獲取指定key對應的value,如果key不存在,會報錯
val leoAge = ages("Leo")
val leoAge = ages("leo")

// 使用contains函式檢查key是否存在
val leoAge = if (ages.contains("leo")) ages("leo") else 0
// getOrElse函式
val leoAge = ages.getOrElse("leo", 0)//該函式優雅簡潔

修改Map

// 更新Map的元素(可變)
ages("Leo") = 31
// 增加多個元素
ages += ("Mike" -> 35, "Tom" -> 40)
// 移除元素
ages -= "Mike"
// 更新不可變的map
val ages2 = ages + ("Mike" -> 36, "Tom" -> 40)
// 移除不可變map的元素
val ages3 = ages - "Tom"

遍歷Map

// 遍歷map的entrySet
for ((key, value) <- ages) println(key + " " + value)
// 遍歷map的key
for (key <- ages.keySet) println(key)
// 遍歷map的value
for (value <- ages.values) println(value)
// 生成新map,反轉key和value
for ((key, value) <- ages) yield (value, key)

特殊Map

// SortedMap可以自動對Map的key的排序
val ages = scala.collection.immutable.SortedMap("leo" -> 30, "alice" -> 15, "jen" -> 25)

// LinkedHashMap可以記住插入entry的順序
val ages = new scala.collection.mutable.LinkedHashMap[String, Int]

Tuple

// Tuple是一個元組,但不一定是兩個,可以是多個
val t = (“leo”, 30)
image_1ap53nh5iqhk8ta1r239o71s013h.png-25.2kB
// 訪問Tuple
t._1

// zip操作
val names = Array(“leo”, “jack”, “mike”)
val ages = Array(30, 24, 26)
val nameAges = names.zip(ages)
for ((name, age) <- nameAges) println(name + “: ” + age)
image_1ap53vjha1me7jld17ld1m381nm43u.png-23.2kB

面對物件程式設計之–類

1.定義一個簡單的類

// 定義類,包含field以及方法

class HelloWorld {
  private var name = "leo"
  def sayHello() { print("Hello, " + name) }  
  def getName = name
}

// 建立類的物件,並呼叫其方法
val helloWorld = new HelloWorld
helloWorld.sayHello()
print(helloWorld.getName)
//也可以不加括號,如果定義方法時不帶括號,則呼叫方法時也不能帶括號

2.getter&setter

// 定義不帶private的var field,此時scala生成的面向JVM的類時,會定義為private的name欄位,並提供public的getter和setter方法
// 而如果使用private修飾field,則生成的getter和setter也是private的
// 如果定義val field,則只會生成getter方法
// 如果不希望生成setter和getter方法,則將field宣告為private[this]
class Student {
var name = "leo"
}

// 呼叫getter和setter方法,分別叫做name和name_ =
val leo = new Student
print(leo.name)
leo.name = "leo1"

// 如果只是希望擁有簡單的getter和setter方法,那麼就按照scala提供的語法規則,根據需求為field選擇合適的修飾符就好:var、val、private、private[this]
// 但是如果希望能夠自己對getter與setter進行控制,則可以自定義getter與setter方法
// 自定義setter方法的時候一定要注意scala的語法限制,簽名、=、引數間不能有空格

class Student {
  private var myName = "leo"
  def name = "your name is " + myName
  def name_=(newValue: String)  {
    print("you cannot edit your name!!!")
  }
}

val leo = new Student
print(leo.name)
leo.name = "leo1"

// 如果你不希望field有setter方法,則可以定義為val,但是此時就再也不能更改field的值了
// 但是如果希望能夠僅僅暴露出一個getter方法,並且還能通過某些方法更改field的值,那麼需要綜合使用private以及自定義getter方法
// 此時,由於field是private的,所以setter和getter都是private,對外界沒有暴露;自己可以實現修改field值的方法;自己可以覆蓋getter方法

class Student {
  private var myName = "leo"

  def updateName(newName: String) { 
    if(newName == "leo1") myName = newName 
    else print("not accept this new name!!!")
  }

  def name = "your name is " + myName
}

// 如果將field使用private來修飾,那麼代表這個field是類私有的,在類的方法中,可以直接訪問類的其他物件的private field
// 這種情況下,如果不希望field被其他物件訪問到,那麼可以使用private[this],意味著物件私有的field,只有本物件內可以訪問到

class Student {
  private var myAge = 0
  def age_=(newValue: Int) { 
    if (newValue > 0) myAge = newValue 
    else print("illegal age!") 
  }
  def age = myAge
  def older(s: Student) = {
    myAge > s.myAge
  }
}

3.輔助constructor

// Scala中,可以給類定義多個輔助constructor,類似於java中的建構函式過載
// 輔助constructor之間可以互相呼叫,而且必須第一行呼叫主constructor

class Student {
  private var name = ""
  private var age = 0
  def this(name: String) {
    this()
    this.name = name
  }
  def this(name: String, age: Int) {
    this(name)
    this.age = age
  }
}

4.主constructor

// Scala中,主constructor是與類名放在一起的,與java不同
// 而且類中,沒有定義在任何方法或者是程式碼塊之中的程式碼,就是主constructor的程式碼,這點感覺沒有java那麼清晰
class Student(val name: String, val age: Int) {
println("your name is " + name + ", your age is " + age)
}

image_1apagnfep1k7k1kdfsoslto85r9.png-48.3kB
// 主constructor中還可以通過使用預設引數,來給引數預設的值
class Student(val name: String = "leo", val age: Int = 30) {
println("your name is " + name + ", your age is " + age)
}

image_1apagqo4i1arj13j3rtl197halnm.png-52.2kB
// 如果主constrcutor傳入的引數什麼修飾都沒有,比如name: String,那麼如果類內部的方法使用到了,則會宣告為private[this] name;否則沒有該field,就只能被constructor程式碼使用而已

5.內部類

// Scala中,同樣可以在類中定義內部類;但是與java不同的是,每個外部類的物件的內部類,都是不同的類

import scala.collection.mutable.ArrayBuffer
class Class {
  class Student(val name: String) {}
  val students = new ArrayBuffer[Student]
  def getStudent(name: String) =  {
    new Student(name)
  }
}

val c1 = new Class
val s1 = c1.getStudent("leo")
c1.students += s1

val c2 = new Class
val s2 = c2.getStudent("leo")
c1.students += s2

image_1apahuc9919jkie21qp61i8c1quo13.png-24.8kB
c1的內部類與c2的內部類型別不同

面向物件程式設計之物件

  • Object
    // object,相當於class的單個例項,通常在裡面放一些靜態的field或者method
    // 第一次呼叫object的方法時,就會執行object的constructor,也就是object內部不在method中的程式碼;但是object不能定義接受引數的constructor
    // 注意,object的constructor只會在其第一次被呼叫時執行一次,以後再次呼叫就不會再次執行constructor了
    // object通常用於作為單例模式的實現,或者放class的靜態成員,比如工具方法
object Person {
  private var eyeNum = 2
  println("this Person object!")
  def getEyeNum = eyeNum
}

image_1apaibu2h14on10chum51gia8001g.png-26.7kB

  • 伴生物件
    // 如果有一個class,還有一個與class同名的object,那麼就稱這個object是class的伴生物件,class是object的伴生類
    // 伴生類和伴生物件必須存放在一個.scala檔案之中
    // 伴生類和伴生物件,最大的特點就在於,互相可以訪問private field
object Person {
  private val eyeNum = 2
  def getEyeNum = eyeNum
}

class Person(val name: String, val age: Int) {
  def sayHello = println("Hi, " + name + ", I guess you are " + age + " years old!" + ", and usually you must have " + Person.eyeNum + " eyes.")
}
  • 讓object繼承抽象類
    // object的功能其實和class類似,除了不能定義接受引數的constructor之外
    // object也可以繼承抽象類,並覆蓋抽象類中的方法
abstract class Hello(var message: String) {
  def sayHello(name: String): Unit
}

object HelloImpl extends Hello("hello") {
  override def sayHello(name: String) = {
    println(message + ", " + name)
  }
}

測試:image_1apc697a7a201je1eg094rb4f9.png-9.7kB

  • apply()方法
    // object中非常重要的一個特殊方法,就是apply方法
    // 通常在伴生物件中實現apply方法,並在其中實現構造伴生類的物件的功能
    // 而建立伴生類的物件時,通常不會使用new Class的方式,而是使用Class()的方式,隱式地呼叫伴生物件得apply方法,這樣會讓物件建立更加簡潔

// 比如,Array類的伴生物件的apply方法就實現了接收可變數量的引數,並建立一個Array物件的功能
val a = Array(1, 2, 3, 4, 5)

// 比如,定義自己的伴生類和伴生物件

class Person(val name: String)
object Person {
  def apply(name: String) = new Person(name)
}

image_1apc6j3d61h27g2fsfictd18dfm.png-12.5kB

  • main方法
    // 就如同java中,如果要執行一個程式,必須編寫一個包含main方法類一樣;在scala中,如果要執行一個應用程式,那麼必須有一個main方法,作為入口
    // scala中的main方法定義為def main(args: Array[String]),而且必須定義在object中
object HelloWorld {
  def main(args: Array[String]) {
    println("Hello World!!!")
  }
}

*// 除了自己實現main方法之外,還可以繼承App Trait,然後將需要在main方法中執行的程式碼,直接作為object的constructor程式碼;而且用args可以接受傳入的引數
object HelloWorld extends App {
if (args.length > 0) println(“hello, ” + args(0))
else println(“Hello World!!!”)
}
// 如果要執行上述程式碼,需要將其放入.scala檔案,然後先使用scalac編譯,再用scala執行
scalac HelloWorld.scala
scala -Dscala.time HelloWorld
// App Trait的工作原理為:App Trait繼承自DelayedInit Trait,scalac命令進行編譯時,會把繼承App Trait的object的constructor程式碼都放到DelayedInit Trait的delayedInit方法中執行*

  • 使用object來實現列舉值
    // Scala沒有直接提供類似於Java中的Enum這樣的列舉特性,如果要實現列舉,則需要用object繼承Enumeration類,並且呼叫Value方法來初始化列舉值
    object Season extends Enumeration {
    val SPRING, SUMMER, AUTUMN, WINTER = Value
    }

    image_1apc7u3u910dt1jc81qke6vtt9t13.png-11.9kB
    // 還可以通過Value傳入列舉值的id和name,通過id和toString可以獲取; 還可以通過id和name來查詢列舉值
    object Season extends Enumeration {
    val SPRING = Value(0, "spring")
    val SUMMER = Value(1, "summer")
    val AUTUMN = Value(2, "autumn")
    val WINTER = Value(3, "winter")
    }
    Season(0)
    Season.withName("spring")

    image_1apc80gds1pfmqp7ejk1sqijtt1g.png-37.1kB
    // 使用列舉object.values可以遍歷列舉值
    for (ele <- Season.values) println(ele)
    image_1apc823malaj1at9lk51eivr8c1t.png-18.4kB

面向物件之繼承

  • extends
    // Scala中,讓子類繼承父類,與Java一樣,也是使用extends關鍵字
    // 繼承就代表,子類可以從父類繼承父類的field和method;然後子類可以在自己內部放入父類所沒有,子類特有的field和method;使用繼承可以有效複用程式碼
    // 子類可以覆蓋父類的field和method;但是如果父類用final修飾,field和method用final修飾,則該類是無法被繼承的,field和method是無法被覆蓋的
class Person {
  private var name = "leo"
  def getName = name
}
class Student extends Person {
  private var score = "A"
  def getScore = score
}

image_1apc8ibbuv0g1ktl1nik64ab22a.png-30.4kB

  • override & super

    // Scala中,如果子類要覆蓋一個父類中的非抽象方法,則必須使用override關鍵字
    // override關鍵字可以幫助我們儘早地發現程式碼裡的錯誤,比如:override修飾的父類方法的方法名我們拼寫錯了;比如要覆蓋的父類方法的引數我們寫錯了;等等
    // 此外,在子類覆蓋父類方法之後,如果我們在子類中就是要呼叫父類的被覆蓋的方法呢?那就可以使用super關鍵字,顯式地指定要呼叫父類的方法

class Person {
  private var name = "leo"
  def getName = name
}
class Student extends Person {
  private var score = "A"
  def getScore = score
  override def getName = "Hi, I'm " + super.getName
}

// Scala中,子類可以覆蓋父類的val field,而且子類的val field還可以覆蓋父類的val field的getter方法;只要在子類中使用override關鍵字即可

class Person {
  val name: String = "Person"
  def age: Int = 0
}

class Student extends Person {
  override val name: String = "leo"
  override val age: Int = 30
  }
  • isInstanceOf & asInstanceOf
    // 如果我們建立了子類的物件,但是又將其賦予了父類型別的變數。則在後續的程式中,我們又需要將父類型別的變數轉換為子類型別的變數,應該如何做?
    // 首先,需要使用isInstanceOf判斷物件是否是指定類的物件,如果是的話,則可以使用asInstanceOf將物件轉換為指定型別
    // 注意,如果物件是null,則isInstanceOf一定返回false,asInstanceOf一定返回null
    // 注意,如果沒有用isInstanceOf先判斷物件是否為指定類的例項,就直接用asInstanceOf轉換,則可能會丟擲異常
class Person
class Student extends Person
val p: Person =  new Student
var s: Student = null
if (p.isInstanceOf[Student]) s = p.asInstanceOf[Student]
  • getClass & classOf
    // isInstanceOf只能判斷出物件是否是指定類以及其子類的物件,而不能精確判斷出,物件就是指定類的物件
    // 如果要求精確地判斷物件就是指定類的物件,那麼就只能使用getClass和classOf了
    // 物件.getClass可以精確獲取物件的類,classOf[類]可以精確獲取類,然後使用==操作符即可判斷
class Person
class Student extends Person
val p: Person = new Student
p.isInstanceOf[Person]
p.getClass == classOf[Person]
p.getClass == classOf[Student]

image_1apcah6r7upd1d78jcj1e7e1pg92n.png-25.9kB

  • 使用模式匹配進行型別判斷

    // 但是在實際開發中,比如spark的原始碼中,大量的地方都是使用了模式匹配的方式來進行型別的判斷,這種方式更加地簡潔明瞭,而且程式碼得可維護性和可擴充套件性也非常的高
    // 使用模式匹配,功能性上來說,與isInstanceOf一樣,也是判斷主要是該類以及該類的子類的物件即可,不是精準判斷的

class Person
class Student extends Person
val p: Person = new Student

p match {
  case per: Person => println("it's Person's object")
  case _  => println("unknown type")
}
  • protected

    // 跟java一樣,scala中同樣可以使用protected關鍵字來修飾field和method,這樣在子類中就不需要super關鍵字,直接就可以訪問field和method
    // 還可以使用protected[this],則只能在當前子類物件中訪問父類的field和method,無法通過其他子類物件訪問父類的field和method

class Person {
  protected var name: String = "leo"
  protected[this] var hobby: String = "game"
} 
class Student extends Person {
  def sayHello = println("Hello, " + name)
  def makeFriends(s: Student) {
    println("my hobby is " + hobby + ", your hobby is " + s.hobby)
  }
}
  • 呼叫父類的constructor
    // Scala中,每個類可以有一個主constructor和任意多個輔助constructor,而每個輔助constructor的第一行都必須是呼叫其他輔助constructor或者是主constructor;因此子類的輔助constructor是一定不可能直接呼叫父類的constructor的
    // 只能在子類的主constructor中呼叫父類的constructor,以下這種語法,就是通過子類的主建構函式來呼叫父類的建構函式
    // 注意!如果是父類中接收的引數,比如name和age,子類中接收時,就不要用任何val或var來修飾了,否則會認為是子類要覆蓋父類的field
class Person(val name: String, val age: Int)
class Student(name: String, age: Int, var score: Double) extends Person(name, age) {
  def this(name: String) {
    this(name, 0, 0)
  }
  def this(age: Int) {
    this("leo", age, 0)
  }
}
  • 匿名內部類

    // 在Scala中,匿名子類是非常常見,而且非常強大的。Spark的原始碼中也大量使用了這種匿名子類。
    // 匿名子類,也就是說,可以定義一個類的沒有名稱的子類,並直接建立其物件,然後將物件的引用賦予一個變數。之後甚至可以將該匿名子類的物件傳遞給其他函式。

class Person(protected val name: String) {
  def sayHello = "Hello, I'm " + name
}
val p = new Person("leo") {
  override def sayHello = "Hi, I'm " + name
}
def greeting(p: Person { def sayHello: String }) {
  println(p.sayHello)
}

image_1apcmifnntoj1aa21pot1cbkdrd34.png-81.5kB

  • 抽象類

    // 如果在父類中,有某些方法無法立即實現,而需要依賴不同的子來來覆蓋,重寫實現自己不同的方法實現。此時可以將父類中的這些方法不給出具體的實現,只有方法簽名,這種方法就是抽象方法。
    // 而一個類中如果有一個抽象方法,那麼類就必須用abstract來宣告為抽象類,此時抽象類是不可以例項化的
    // 在子類中覆蓋抽象類的抽象方法時,不需要使用override關鍵字

abstract class Person(val name: String) {
  def sayHello: Unit
}
class Student(name: String) extends Person(name) {
  def sayHello: Unit = println("Hello, " + name)
}
  • 抽象field

    // 如果在父類中,定義了field,但是沒有給出初始值,則此field為抽象field
    // 抽象field意味著,scala會根據自己的規則,為var或val型別的field生成對應的getter和setter方法,但是父類中是沒有該field的
    // 子類必須覆蓋field,以定義自己的具體field,並且覆蓋抽象field,不需要使用override關鍵字

abstract class Person {
  val name: String
}

class Student extends Person {
  val name: String = "leo"
}

面向物件程式設計之Trait

  1. trait 作為介面

    // Scala中的Triat是一種特殊的概念
    // 首先我們可以將Trait作為介面來使用,此時的Triat就與Java中的介面非常類似
    // 在triat中可以定義抽象方法,就與抽象類中的抽象方法一樣,只要不給出方法的具體實現即可
    // 類可以使用extends關鍵字繼承trait,注意,這裡不是implement,而是extends,在scala中沒有implement的概念,無論繼承類還是trait,統一都是extends
    // 類繼承trait後,必須實現其中的抽象方法,實現時不需要使用override關鍵字
    // scala不支援對類進行多繼承,但是支援多重繼承trait,使用with關鍵字即可

trait HelloTrait {
  def sayHello(name: String)
}
trait MakeFriendsTrait {
  def makeFriends(p: Person)
}
class Person(val name: String) extends HelloTrait with MakeFriendsTrait with Cloneable with Serializable {
  def sayHello(na