首页 > 代码库 > Scala学习笔记(1)
Scala学习笔记(1)
为什么选择Scala?
- 表达能力
- 函数是一等公民
- 闭包
- 简洁
- 类型推断
- 函数创建的文法支持
- Java
- 可重用java库
- 可重用java工具
- 没有性能惩罚
Scala如何工作?
编译成Java字节码
可在任何标准JVM上运行
甚至是在一些不规范的JVM上,如Dalvik
Scala编译器是Java编译器的作者写的
下载和安装
可以在http://www.scala-lang.org/上下载最新的scala安装包。下载后解压,然后切换到scala所在目录,执行:
$ cd /export/scala/scala-2.11.4
$ bin/scala
Picked up _JAVA_OPTIONS: -Dawt.useSystemAAFontSettings=gasp
Welcome to Scala version 2.11.4 (Java HotSpot(TM) 64-Bit Server VM, Java 1.6.0_45).
Type in expressions to have them evaluated.
Type :help for more information.
scala>
表达式和值
在Scala中,几乎所有的语言元素都是表达式。
scala> println("hello wolrd")
hello wolrd
是一个表达式,
scala> "hello"+" world"
res2: String = hello world
也是一个表达式。
可以通过val定义一个常量,亦可以通过var定义一个变量。推荐多使用常量。
函数是一等公民
可以使用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))
如果函数不带参数,你可以不写括号。
scala> def three() = 1 + 2
three: ()Int
scala> three()
res3: Int = 3
scala> three
res4: Int = 3
匿名函数
你可以创建匿名函数
scala> (x: Int)=>x+1
res5: Int => Int = <function1>
scala> res5(4)
res6: Int = 5
你可以传递匿名函数,或将其保存成不变量。
scala> val addOne = (x: Int)=>x+1
addOne: Int => Int = <function1>
scala> addOne(3)
res7: Int = 4
部分应用
你可以使用下划线"_"部分应用一个函数,结果将得到另一个函数。Scala使用下划线表示不同上下文中的不同事物,你通常可以把它看做是一个没有命名的神奇通配符。在( _ + 2)的上下文钟,它代表一个匿名参数。你可以这样使用它:
scala> def adder(m: Int, n: Int) = m + n
adder: (m: Int, n: Int)Int
scala> val add2 = adder(2, _:Int)
add2: Int => Int = <function1>
scala> add2(3)
res8: Int = 5
你可以部分应用参数列表中的任意参数,而不仅仅是最后一个。
柯里化函数
有时候会有这样的需求:允许别人一会在你的函数上应用一些参数,然后又应用另外的一些参数。
例如一个乘法函数,在一个场景需要选择乘数,而另一个场景需要选择被乘数。
scala> def multiply(m: Int)(n: Int) : Int = m*n
multiply: (m: Int)(n: Int)Int
你可以直接传入两个参数。
scala> multiply(2)(3)
res9: Int = 6
也可以填上第一个参数并且部分应用第二个参数。
scala> val timesTwo = multiply(2) _
timesTwo: Int => Int = <function1>
scala> timesTwo(3)
res10: Int = 6
可变长度参数
这是一个特殊的语法,可以向方法传入任意多个同类型的参数。例如要在多个字符串上执行String的capitalize函数,可以这样写:
scala> def capitalizeAll(args: String*) = {
| args.map { arg =>
| arg.capitalize
| }
| }
capitalizeAll: (args: String*)Seq[String]
scala> capitalizeAll("rarity", "applejack")
res11: Seq[String] = ArrayBuffer(Rarity, Applejack)
借贷模式
由于函数可以像值一样作为参数传递,所以可以方便的实现借贷模式。
下面的例子从/proc/self/stat文件中读取当前进程的pid。
withScannerf封装了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,所以运行该程序会长生异常。
scala> var logEnable = false
logEnable: Boolean = false
scala> def log(msg: String) =
| if (logEnable) println(msg)
log: (msg: String)Unit
scala> val MSG = "programming is running"
MSG: String = programming is running
scala> log(MSG + 1/0)
java.lang.ArithmeticException: / by zero
... 33 elided
将def log(msg: String)修改成def log(msg: => String),由按值传递改为按名称传递后将不会产生异常。
因为log函数的参数是按名称传递,参数会等到实际使用的时候才会计算,所以被跳过。
scala> def log(msg: => String) =
| if (logEnable) println(msg)
log: (msg: => String)Unit
scala> log(MSG + 1/0)
按名称传递参数可以减少不必要的计算和异常。
定义类
可以用class关键字来定义类。并通过new来创建类。
在定义类时可以定义字段,如firstName,lastName。这样做还可以自动生成构造函数。
可以在类中通过def定义函数。var和val定义字段。
函数名是任何字符如+,-,*,/。
试着将
obama.age_=(51)
简化为
obama.age = 51
这样的简化更像调用一个变量。
scala> class Person(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()
| }
defined class Person
scala> val obama: Person = new Person("Barack", "obama")
obama: Person = Barack obama
scala> println("Person" + obama)
PersonBarack obama
scala> println("firstName:" + obama.firstName)
firstName:Barack
scala> println("lastName:" + obama.lastName)
lastName:obama
scala> obama.age_=(51)
scala> println("age:" + obama.age)
age:51
旁白: 函数 vs 方法
函数和方法在很大程度上是可以互换的。由于函数和方法是如此的相似,你可能都不知道你调用的东西是一个函数还是一个方法。而当真正碰到的方法和函数之间的差异的时候,你可能会感到困惑。
scala> class C {
| var acc = 0
| def minc = { acc += 1 }
| val finc = { () => acc += 1 }
| }
defined class C
scala> val c = new C
c: C = C@1af1bd6
scala> c.minc // calls c.minc()
scala> c.finc // returns the function as a value:
res2: () => Unit = <function0>
当你可以调用一个不带括号的“函数”,但是对另一个却必须加上括号的时候,你可能会想哎呀,我还以为自己知道Scala是怎么工作的呢。也许他们有时需要括号?你可能以为自己用的是函数,但实际使用的是方法。
在实践中,即使不理解方法和函数上的区别,你也可以用Scala做伟大的事情。如果你是Scala新手,而且在读两者的差异解释,你可能会跟不上。不过这并不意味着你在使用Scala上有麻烦。它只是意味着函数和方法之间的差异是很微妙的,只有深入语言内部才能清楚理解它。
继承
class SuperMan(brand: String) extends Person(brand) {
def fly() = "fly"
}
参考 Effective Scala 指出如果子类与父类实际上没有区别,类型别名是优于继承的。A Tour of Scala 详细介绍了子类化。
重载方法
class SuperMan(brand: String) extends Person(brand) {
def fullName() = firstName + " " + lastName
}
抽象类
你可以定义一个抽象类,它定义了一些方法但没有实现它们。取而代之是由扩展抽象类的子类定义这些方法。你不能创建抽象类的实例。
scala> abstract class Shape {
| def getArea():Int // subclass should define this
| }
defined class Shape
scala> class Circle(r: Int) extends Shape {
| def getArea():Int = { r * r * 3 }
| }
defined class Circle
scala> val s = new Shape
<console>:8: error: class Shape is abstract; cannot be instantiated
val s = new Shape
^
scala> val c = new Circle(2)
c: Circle = Circle@65c0035b
特质(Traits)
特质是一些字段和行为的集合,可以扩展或混入(mixin)你的类中。
trait Car {
val brand: String
}
trait Shiny {
val shineRefraction: Int
}
class BMW extends Car {
val brand = "BMW"
}
通过with关键字,一个类可以扩展多个特质:
class BMW extends Car with Shiny {
val brand = "BMW"
val shineRefraction = 12
}
参考 Effective Scala 对特质的观点。
什么时候应该使用特质而不是抽象类? 如果你想定义一个类似接口的类型,你可能会在特质和抽象类之间难以取舍。这两种形式都可以让你定义一个类型的一些行为,并要求继承者定义一些其他行为。一些经验法则:
优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。
如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。例如,你不能说trait t(i: Int) {},参数i是非法的。
你不是问这个问题的第一人。可以查看更全面的答案: stackoverflow: Scala特质 vs 抽象类 , 抽象类和特质的区别, and Scala编程: 用特质,还是不用特质?
类型
此前,我们定义了一个函数的参数为Int,表示输入是一个数字类型。其实函数也可以是泛型的,来适用于所有类型。当这种情况发生时,你会看到用方括号语法引入的类型参数。下面的例子展示了一个使用泛型键和值的缓存。
trait Cache[K, V] {
def get(key: K): V
def put(key: K, value: V)
def delete(key: K)
}
方法也可以引入类型参数。
def remove[K](key: K)
转载请注明出处:http://blog.csdn.net/iAm333
Scala学习笔记(1)