Scala——基礎篇
表示式和值
在Scala中,幾乎所有的語言元素都是表示式。
println("hello wolrd")
是一個表示式,
"hello"+" world"
也是一個表示式。
可以通過val定義一個常量,亦可以通過var定義一個變數,推薦多使用常量。
var helloWorld = "hello" + " world"
println(helloWorld)
val again = " again"
helloWorld = helloWorld + again
println(helloWorld)
函式是一等公民
可以使用def來定義一個函式。函式體是一個表示式。
使用Block表示式的時候,預設最後一行的返回是返回值,無需顯式指定。
函式還可以像值一樣,賦值給var或val。因此函式也可以作為引數傳給另一個函式。
def square(a: Int) = a * a def squareWithBlock(a: Int) = { a * a } val squareVal = (a: Int) => a * a def addOne(f: Int => Int, arg: Int) = f(arg) + 1 println("square(2):" + square(2)) println("squareWithBlock(2):" + squareWithBlock(2)) println("squareVal(2):" + squareVal(2)) println("addOne(squareVal,2):" + addOne(squareVal, 2))
借貸模式
由於函式可以像值一樣作為引數傳遞,所以可以方便的實現借貸模式。
這個例子是從/proc/self/stat檔案中讀取當前程序的pid。
withScanner封裝了try-finally塊,所以呼叫者不用再close。
注:當表示式沒有返回值時,預設返回Unit。
import scala.reflect.io.File import java.util.Scanner def withScanner(f: File, op: Scanner => Unit) = { val scanner = new Scanner(f.bufferedReader) try { op(scanner) } finally { scanner.close() } } withScanner(File("/proc/self/stat"), scanner => println("pid is " + scanner.next()))
按名稱傳遞引數
這個例子演示了按名稱傳遞引數,由於有除以0,所以執行該程式會產生異常。
試著將
def log(msg: String)
修改為
def log(msg: => String)
由按值傳遞修改為按名稱傳遞後將不會產生異常。
因為log函式的引數是按名稱傳遞,引數會等到實際使用的時候才會計算,所以被跳過。
按名稱傳遞引數可以減少不必要的計算和異常。
val logEnable = false
def log(msg: String) =
if (logEnable) println(msg)
val MSG = "programing is running"
log(MSG + 1 / 0)
定義類
可以用class關鍵字來定義類。並通過new來建立類。
在定義類時可以定義欄位,如firstName,lastName。這樣做還可以自動生成建構函式。
可以在類中通過def定義函式。var和val定義欄位。
函式名是任何字元如+,-,*,/。
試著將
obama.age_=(51)
簡化為
obama.age = 51
這樣的簡化更像呼叫一個變數。
class Persion(val firstName: String, val lastName: String) {
private var _age = 0
def age = _age
def age_=(newAge: Int) = _age = newAge
def fullName() = firstName + " " + lastName
override def toString() = fullName()
}
val obama: Persion = new Persion("Barack", "Obama")
println("Persion: " + obama)
println("firstName: " + obama.firstName)
println("lastName: " + obama.lastName)
obama.age_=(51)
println("age: " + obama.age)
鴨子型別
走起來像鴨子,叫起來像鴨子,就是鴨子。
這個例子中使用
{ def close(): Unit }
作為引數型別。因此任何含有close()的函式的類都可以作為引數。
不必使用繼承這種不夠靈活的特性。
def withClose(closeAble: { def close(): Unit },
op: { def close(): Unit } => Unit) {
try {
op(closeAble)
} finally {
closeAble.close()
}
}
class Connection {
def close() = println("close Connection")
}
val conn: Connection = new Connection()
withClose(conn, conn =>
println("do something with Connection"))
柯里化
這個例子和上面的功能相同。不同的是使用了柯里化(Currying)技術。
def add(x:Int, y:Int) = x + y
是普通的函式
def add(x:Int) = (y:Int) => x + y
是柯里化後的函式,相當於返回一個匿名函式表示式。
def add(x:Int)(y:Int) = x + y
是簡化寫法
柯里化可以讓我們構造出更像原生語言提供的功能的程式碼
試著將例子中的withclose(...)(...)換成withclose(...){...}
def withClose(closeAble: { def close(): Unit })
(op: { def close(): Unit } => Unit) {
try {
op(closeAble)
} finally {
closeAble.close()
}
}
class Connection {
def close() = println("close Connection")
}
val conn: Connection = new Connection()
withClose(conn)(conn =>
println("do something with Connection"))
範型
之前的例子可以使用泛型變得更簡潔更靈活。
試著將
"123456"
修改為
123456
雖然msg由String型別變為Int型別,但是由於使用了泛型,程式碼依舊可以正常執行。
def withClose[A <: { def close(): Unit }, B](closeAble: A)
(f: A => B): B =
try {
f(closeAble)
} finally {
closeAble.close()
}
class Connection {
def close() = println("close Connection")
}
val conn: Connection = new Connection()
val msg = withClose(conn) { conn =>
{
println("do something with Connection")
"123456"
}
}
println(msg)
Traits
Traits就像是有函式體的Interface。使用with關鍵字來混入。
這個例子是給java.util.ArrayList添加了foreach的功能。
試著再在with ForEachAble[Int]後面加上
with JsonAble
給list新增toJson的能力
trait ForEachAble[A] {
def iterator: java.util.Iterator[A]
def foreach(f: A => Unit) = {
val iter = iterator
while (iter.hasNext)
f(iter.next)
}
}
trait JsonAble {
def toJson() =
scala.util.parsing.json.JSONFormat.defaultFormatter(this)
}
val list = new java.util.ArrayList[Int]() with ForEachAble[Int]
list.add(1); list.add(2)
println("For each: "); list.foreach(x => println(x))
//println("Json: " + list.toJson())