Scala程序中的分号推断和Singleton对象

开发 后端
本文节选自Martin Odersky,Lex Spoon和Bill Venners所著,Regular翻译的《Programming in Scala》的第四章。Scala是一种针对 JVM 将函数和面向对象技术组合在一起的编程语言。

分号推断

Scala程序里,语句末尾的分号通常是可选的。如果你愿意可以输入一个,但若一行里仅有一个语句也可不写。另一方面,如果一行里写多个语句那么分号是需要的:

51CTO编辑推荐:Scala编程语言专题

  1. val s = "hello"; println(s) 

如果你想输入一个跨越多行的语句,多数时候你只需输入,Scala将在正确的位置分隔语句。例如,下面的代码被认为是一个跨四行的语句:

  1. if (x < 2)  
  2.  println("too small")  
  3. else 
  4.  println("ok")  
然而,偶尔Scala也许没有按照你的愿望把句子分割成两部分:

  1. x  
  2. + y  
这会被分成两个语句x和+ y。如果你希望把它作为一个语句x + y,你可以把它包裹在括号里:

  1. (x  
  2. + y)  
或者,你也可以把+放在行末。正是由于这个原因,当你在串接类似于+的中缀操作符,把操作符放在行尾而不是行头是普遍的Scala风格:

  1. x +  
  2. y +  
  3. z  
分号推断的规则

分割语句的精确规则非常有效却出人意料的简单。那就是,除非以下情况的一种成立,否则行尾被认为是一个分号:

1.疑问行由一个不能合法作为语句结尾的字结束,如句点或中缀操作符。

2.下一行开始于不能作为语句开始的字。

3.行结束于括号(...)或方框[...]内部,因为这些符号不可能容纳多个语句。

Singleton对象

如第1章所提到的,Scala比Java更面向对象的一个方面是Scala没有静态成员。替代品是,Scala有单例对象:singleton object。除了用object关键字替换了class关键字以外,单例对象的定义看上去就像是类定义。代码4.2展示了一个例子:

  1. // 文件ChecksumAccumulator.scala  
  2. import scala.collection.mutable.Map  
  3. object ChecksumAccumulator {  
  4.  private val cache = Map[String, Int]()  
  5.  def calculate(s: String): Int =  
  6.   if (cache.contains(s))  
  7.    cache(s)  
  8.   else {  
  9.    val acc = new ChecksumAccumulator  
  10.    for (c <- s)  
  11.     acc.add(c.toByte)  
  12.    val cs = acc.checksum()  
  13.    cache += (s -> cs)  
  14.    cs  
  15.   }  
  16. }  
代码 4.2 类ChecksumAccumulator的伴生对象

表中的单例对象被叫做ChecksumAccumulator,与前一个例子里的类同名。当单例对象与某个类共享同一个名称时,他被称作是这个类的伴生对象:companion object。你必须在同一个源文件里定义类和它的伴生对象。类被称为是这个单例对象的伴生类:companion class。类和它的伴生对象可以互相访问其私有成员。

ChecksumAccumulator单例对象有一个方法,calculate,用来计算所带的String参数中字符的校验和。它还有一个私有字段,cache,一个缓存之前计算过的校验和的可变映射。这里我们使用了缓存例子来说明带有域的单例对象。像这样的缓存是通过内存换计算时间的方式做到性能的优化。通常意义上说,只有遇到了缓存能解决的性能问题时,才可能用到这样的例子,而且应该使用弱映射(weak map),如scala.Collection.jcl的WeakHashMap,这样如果内存稀缺的话,缓存里的条目就会被垃圾回收机制回收掉。 方法的第一行,“if (cache.contains(s))”,检查缓存,看看是否传递进来的字串已经作为键存在于映射当中。如果是,就仅仅返回映射的值,“cache(s)”。否则,执行else子句,计算校验和。else子句的第一行定义了一个叫acc的val并用新建的ChecksumAccumulator实例初始化它。因为关键字new只用来实例化类,所以这里创造的新对象是ChecksumAccumulator类的一个实例,而不是同名的单例对象。下一行是个for表达式,对传入字串的每个字符循环一次,并在其上调用toByte把字符转换成Byte,然后传递给acc所指的ChecksumAccumulator实例的add方法。完成了for表达式后,下一行的方法在acc上调用checksum,获得传入字串的校验和,并存入叫做cs的val。下一行,“cache += (s -> cs)”,传入的字串键映射到整数的校验和值,并把这个键-值对加入cache映射。方法的最后一个表达式,“cs”,保证了校验和为此方法的结果。

如果你是Java程序员,考虑单例对象的一种方式是把它当作是或许你在Java中写过的任何静态方法之家。可以在单例对象上用类似的语法调用方法:单例对象名,点,方法名。例如,可以如下方式调用ChecksumAccumulator单例对象的calculate方法:

  1. ChecksumAccumulator.calculate("Every value is an object."

然而单例对象不只是静态方法的收容站。它同样是个第一类的对象。因此你可以把单例对象的名字看作是贴在对象上的“名签”:

 可以把单例对象的名字看作是贴在对象上的“名签”

定义单例对象不是定义类型(在Scala的抽象层次上说)。如果只是ChecksumAccumulator对象的定义,你就建不了ChecksumAccumulator类型的变量。宁愿这么说,ChecksumAccumulator类型是由单例对象的伴生类定义的。然而,单例对象扩展了超类并可以混入特质。由于每个单例对象都是超类的实例并混入了特质,你可以通过这些类型调用它的方法,用这些类型的变量指代它,并把它传递给需要这些类型的方法。我们将在第十二章展示一些继承自类和特质的单例对象的例子。

类和单例对象间的一个差别是,单例对象不带参数,而类可以。因为你不能用new关键字实例化一个单例对象,你没机会传递给它参数。每个单例对象都被作为由一个静态变量指向的虚构类:synthetic class的一个实例来实现,因此它们与Java静态类有着相同的初始化语法。虚构类的名字是对象名加上一个美元符号。因此单例对象ChecksumAccumulator的虚构类是ChecksumAccumulator$。特别要指出的是,单例对象会在第一次被访问的时候初始化。

不与伴生类共享名称的单例对象被称为孤立对象:standalone object。由于很多种原因你会用到它,包括把相关的功能方法收集在一起,或定义一个Scala应用的入口点。下一段会说明这个用例。

【相关阅读】

  1. 学习Scala类的定义,字段和方法
  2. 学习Scala脚本:从文件里读取行记录
  3. 学习识别Scala的函数式风格
  4. Scala编程实例:使用Set和Map
  5. Scala编程实例:使用List和Tuple

责任编辑:book05 来源: Artima
相关推荐

2009-07-22 07:53:00

Scala扩展类

2009-07-22 08:52:05

Scala动态绑定

2009-07-08 16:10:24

Scala简介面向对象函数式

2009-09-27 15:29:00

Scala讲座面向对象Scala

2009-07-21 12:18:37

ScalaRational对象toString

2009-07-21 08:21:46

Scala对象相等性

2024-01-26 08:31:49

2009-07-08 16:25:15

Scala的特点类型推断

2009-06-17 13:26:06

scala继承模型

2009-07-22 09:22:20

Scala工厂对象

2009-07-21 14:03:00

Scalaif表达式while循环

2009-09-09 11:14:16

Scala对象

2009-07-22 09:27:04

Scala变高变宽

2010-11-17 11:31:22

Scala基础面向对象Scala

2011-06-28 11:06:16

Scala

2009-06-22 14:26:12

ScalaXML对象

2010-03-11 10:34:22

Scala

2009-07-21 07:30:00

Scala程序Application

2009-06-16 17:54:38

Scala类语法语义

2012-07-18 09:45:32

Java 8ScalaLambda
点赞
收藏

51CTO技术栈公众号