Scala学习笔记(1),scala学习笔记


为什么选择Scala?

  • 表达能力
    • 函数是一等公民
    • 闭包
  • 简洁
    • 类型推断
    • 函数创建的文法支持
  • Java
    • 可重用java库
    • 可重用java工具
    • 没有性能惩罚


Scala如何工作?

  • 编译成Java字节码

  • 可在任何标准JVM上运行

    • 甚至是在一些不规范的JVM上,如Dalvik

    • Scala编译器是Java编译器的作者写的

下载和安装


可以在http://www.scala-lang.org/上下载最新的scala安装包。下载后解压,然后切换到scala所在目录,执行:  

  1. $ cd /export/scala/scala-2.11.4 
  2. $ bin/scala
  3. Picked up _JAVA_OPTIONS:   -Dawt.useSystemAAFontSettings=gasp
  4. Welcome to Scala version 2.11.4 (Java HotSpot(TM64-Bit Server VMJava 1.6.0_45).
  5. Type in expressions to have them evaluated.
  6. Type :help for more information.
  7. scala>

表达式和值

在Scala中,几乎所有的语言元素都是表达式。

  1. scalaprintln("hello wolrd")
  2. hello wolrd

是一个表达式,

  1. scala"hello"+" world"
  2. res2String = hello world

也是一个表达式。

可以通过val定义一个常量,亦可以通过var定义一个变量。推荐多使用常量。

函数是一等公民

可以使用def来定义一个函数。函数体是一个表达式。

使用Block表达式的时候,默认最后一行的返回是返回值,无需显式指定。

函数还可以像值一样,赋值给var或val。因此,函数也可以作为参数传给另一个函数。

  1. def square(aInt) = a * a
  2. def squareWithBlock(aInt) = {
  3.     a * a
  4. }
  5. val squareVal = (aInt) => a * a
  6. def addOne(fInt => IntargInt) = f(arg) + 1
  7. println("square(2):" + square(2))
  8. println("squareWithBlock(2)" + squareWithBlock(2))
  9. println("squareVal(2):" + squareVal(2))
  10. println("addOne(squareVal,2)" + addOne(squareVal,2))

如果函数不带参数,你可以不写括号。

  1. scaladef three() = 1 + 2
  2. three: ()Int
  3. scalathree()
  4. res3Int = 3
  5. scalathree
  6. res4Int = 3

匿名函数

你可以创建匿名函数

  1. scala> (xInt)=>x+1
  2. res5Int => Int = <function1>
  3. scalares5(4)
  4. res6Int = 5

你可以传递匿名函数,或将其保存成不变量。

  1. scalaval addOne = (xInt)=>x+1
  2. addOneInt => Int = <function1>
  3. scalaaddOne(3)
  4. res7Int = 4

部分应用

你可以使用下划线"_"部分应用一个函数,结果将得到另一个函数。Scala使用下划线表示不同上下文中的不同事物,你通常可以把它看做是一个没有命名的神奇通配符。在( _ + 2)的上下文钟,它代表一个匿名参数。你可以这样使用它:

  1. scaladef adder(mIntnInt) = m + n
  2. adder: (mIntnInt)Int
  3. scalaval add2 = adder(2_:Int)
  4. add2Int => Int = <function1>
  5. scalaadd2(3)
  6. res8Int = 5

你可以部分应用参数列表中的任意参数,而不仅仅是最后一个。

柯里化函数

有时候会有这样的需求:允许别人一会在你的函数上应用一些参数,然后又应用另外的一些参数。

例如一个乘法函数,在一个场景需要选择乘数,而另一个场景需要选择被乘数。

  1. scaladef multiply(mInt)(nInt) : Int = m*n
  2. multiply: (mInt)(nInt)Int

你可以直接传入两个参数。

  1. scalamultiply(2)(3)
  2. res9Int = 6

也可以填上第一个参数并且部分应用第二个参数。

  1. scalaval timesTwo = multiply(2_
  2. timesTwoInt => Int = <function1>
  3. scalatimesTwo(3)
  4. res10Int = 6

可变长度参数

这是一个特殊的语法,可以向方法传入任意多个同类型的参数。例如要在多个字符串上执行String的capitalize函数,可以这样写:

  1. scaladef capitalizeAll(argsString*) = {
  2.      |   args.map { arg =>
  3.      |     arg.capitalize
  4.      |   }
  5.      | }
  6. capitalizeAll: (argsString*)Seq[String]
  7. scalacapitalizeAll("rarity""applejack")
  8. res11Seq[String] = ArrayBuffer(RarityApplejack)

借贷模式

由于函数可以像值一样作为参数传递,所以可以方便的实现借贷模式。

下面的例子从/proc/self/stat文件中读取当前进程的pid。

withScannerf封装了try-finally块,所以调用者不用再close。

注意:当表达式没有返回值的时候,默认返回Unit。

  1. import scala.reflect.io.File
  2. import java.util.Scanner
  3. def withScanner(fFileopScanner => Unit) = {
  4.      val scanner = new Scanner(f.bufferedReader)
  5.      try {
  6.          op(scanner)
  7.      } finally {
  8.           scanner.close()
  9.      }
  10. }
  11. withScanner(File("/proc/self/stat"),scanner => println("pid is" + scanner.next()))


按名称传递参数

下面的例子演示按照名称传递参数,由于有除以0,所以运行该程序会长生异常。

  1. scalavar logEnable = false
  2. logEnableBoolean = false
  3. scaladef log(msgString) = 
  4.      | if (logEnableprintln(msg)
  5. log: (msgString)Unit
  6. scalaval MSG = "programming is running"
  7. MSGString = programming is running
  8. scalalog(MSG + 1/0)
  9. java.lang.ArithmeticException: / by zero
  10.   ... 33 elided

def log(msg: String)修改成def log(msg: => String),由按值传递改为按名称传递后将不会产生异常。

因为log函数的参数是按名称传递,参数会等到实际使用的时候才会计算,所以被跳过。

  1. scaladef log(msg: => String) =
  2.      | if (logEnableprintln(msg)
  3. log: (msg: => String)Unit
  4. scalalog(MSG + 1/0)

按名称传递参数可以减少不必要的计算和异常。

定义类

可以用class关键字来定义类。并通过new来创建类。

在定义类时可以定义字段,如firstName,lastName。这样做还可以自动生成构造函数。

可以在类中通过def定义函数。var和val定义字段。

函数名是任何字符如+,-,*,/。

试着将

obama.age_=(51)

简化为

obama.age = 51

这样的简化更像调用一个变量。

  1. scalaclass Person(val firstNameStringval lastNameString) {
  2.      | private var _age = 0
  3.      | def age = _age
  4.      | def age_=(newAgeInt) = _age = newAge
  5.      | 
  6.      | def fullName() = firstName + " " + lastName
  7.      | 
  8.      | override def toString() = fullName()
  9.      | }
  10. defined class Person
  11. scalaval obamaPerson = new Person("Barack""obama")
  12. obamaPerson = Barack obama
  13. scalaprintln("Person" + obama)
  14. PersonBarack obama
  15. scalaprintln("firstName:" + obama.firstName)
  16. firstName:Barack
  17. scalaprintln("lastName:" + obama.lastName)
  18. lastName:obama
  19. scalaobama.age_=(51)
  20. scalaprintln("age:" + obama.age)
  21. age:51

旁白: 函数 vs 方法

函数和方法在很大程度上是可以互换的。由于函数和方法是如此的相似,你可能都不知道你调用的东西是一个函数还是一个方法。而当真正碰到的方法和函数之间的差异的时候,你可能会感到困惑。

  1. scalaclass C {
  2.      |   var acc = 0
  3.      |   def minc = { acc += 1 }
  4.      |   val finc = { () => acc += 1 }
  5.      | }
  6. defined class C
  7. scalaval c = new C
  8. cC = C@1af1bd6
  9. scalac.minc // calls c.minc()
  10. scalac.finc // returns the function as a value:
  11. res2: () => Unit = <function0>

当你可以调用一个不带括号的“函数”,但是对另一个却必须加上括号的时候,你可能会想哎呀,我还以为自己知道Scala是怎么工作的呢。也许他们有时需要括号?你可能以为自己用的是函数,但实际使用的是方法。

在实践中,即使不理解方法和函数上的区别,你也可以用Scala做伟大的事情。如果你是Scala新手,而且在读两者的差异解释,你可能会跟不上。不过这并不意味着你在使用Scala上有麻烦。它只是意味着函数和方法之间的差异是很微妙的,只有深入语言内部才能清楚理解它。

继承

  1. class SuperMan(brandStringextends Person(brand) {
  2.   def fly() = "fly"
  3. }

参考 Effective Scala 指出如果子类与父类实际上没有区别,类型别名是优于继承的。A Tour of Scala 详细介绍了子类化。

重载方法

  1. class SuperMan(brandStringextends Person(brand) { 
  2.   def fullName() = firstName + " " + lastName
  3. }

抽象类

你可以定义一个抽象类,它定义了一些方法但没有实现它们。取而代之是由扩展抽象类的子类定义这些方法。你不能创建抽象类的实例。

  1. scalaabstract class Shape {
  2.      |   def getArea():Int    // subclass should define this
  3.      | }
  4. defined class Shape
  5. scalaclass Circle(rIntextends Shape {
  6.      |   def getArea():Int = { r * r * 3 }
  7.      | }
  8. defined class Circle
  9. scalaval s = new Shape
  10. <console>:8errorclass Shape is abstractcannot be instantiated
  11.        val s = new Shape
  12.                ^
  13. scalaval c = new Circle(2)
  14. cCircle = Circle@65c0035b

特质(Traits)

特质是一些字段和行为的集合,可以扩展或混入(mixin)你的类中。

  1. trait Car {
  2.   val brandString
  3. }
  4. trait Shiny {
  5.   val shineRefractionInt
  6. }
  7. class BMW extends Car {
  8.   val brand = "BMW"
  9. }

通过with关键字,一个类可以扩展多个特质:

  1. class BMW extends Car with Shiny {
  2.   val brand = "BMW"
  3.   val shineRefraction = 12
  4. }

参考 Effective Scala 对特质的观点。

什么时候应该使用特质而不是抽象类? 如果你想定义一个类似接口的类型,你可能会在特质和抽象类之间难以取舍。这两种形式都可以让你定义一个类型的一些行为,并要求继承者定义一些其他行为。一些经验法则:

  • 优先使用特质。一个类扩展多个特质是很方便的,但却只能扩展一个抽象类。

  • 如果你需要构造函数参数,使用抽象类。因为抽象类可以定义带参数的构造函数,而特质不行。例如,你不能说trait t(i: Int) {},参数i是非法的。

你不是问这个问题的第一人。可以查看更全面的答案: stackoverflow: Scala特质 vs 抽象类 , 抽象类和特质的区别, and Scala编程: 用特质,还是不用特质?

类型

此前,我们定义了一个函数的参数为Int,表示输入是一个数字类型。其实函数也可以是泛型的,来适用于所有类型。当这种情况发生时,你会看到用方括号语法引入的类型参数。下面的例子展示了一个使用泛型键和值的缓存。

  1. trait Cache[KV] {
  2.   def get(keyK): V
  3.   def put(keyKvalueV)
  4.   def delete(keyK)
  5. }

方法也可以引入类型参数。

  1. def remove[K](keyK)

转载请注明出处:http://blog.csdn.net/iAm333

相关内容

    暂无相关文章