表达式和值
在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())