[Scala基础]-- 伴生类和伴生对象

Scala比 Java 更面向对象的一个方面是 Scala 没有静态成员。替代品是,Scala 有: 单例对象:singleton object。

除了用 object 关键字替换了 class 关键字以外,单例对象的定义看上去就像是类定义。如下代码展示了一个例子:


[Scala基础]-- 伴生类和伴生对象_第1张图片


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

ChecksumAccumulator 单例对象有一个方法, calculate ,用来计算所带的 String 参数中字
符的校验和。它还有一个私有字段, cache ,一个缓存之前计算过的校验和的可变映射。


2、方法的第一行,“ if (cache.contains(s)) ”,检查缓存,看看是否传递进来的字串已经作为
键存在于映射当中。如果是,就仅仅返回映射的值,“ cache(s) ”。否则,执行 else 子句,
计算校验和。 else 子句的第一行定义了一个叫 acc 的 val 并用新建的 ChecksumAccumulator
实例初始化它。 

3、如果你是 Java 程序员,考虑单例对象的一种方式是把它当作是或许你在 Java 中写过的任
何静态方法之家。可以在单例对象上用类似的语法调用方法:单例对象名,点,方法名。
例如,可以如下方式调用 ChecksumAccumulator 单例对象的 calculate 方法:
下一行是个 for 表达式,对传入字串的每个字符循环一次,并在其上调用
toByte 把字符转换成 Byte ,然后传递给 acc 所指的 ChecksumAccumulator 实例的 add 方法。
完成了 for 表达式后,下一行的方法在 acc 上调用 checksum ,获得传入字串的校验和,并存
入叫做 cs 的 val 。下一行,“ cache += (s -> cs) ”,传入的字串键映射到整数的校验和值,
并把这个键-值对加入 cache 映射。方法的最后一个表达式,“ cs ”,保证了校验和为此方法
的结果。


注释:
1、 这里我们使用了缓存例子来说明带有域的单例对象。像这样的缓存是通过内存换计算时间的方式做到性
能的优化。通常意义上说,只有遇到了缓存能解决的性能问题时,才可能用到这样的例子,而且应该使用
弱映射(weak map),如 scala.Collection.jcl 的 WeakHashMap ,这样如果内存稀缺的话,缓存里的条
目就会被垃圾回收机制回收掉。
2、 因为关键字new 只用来实例化类,所以这里创造的新对象是 ChecksumAccumulator 类的一个实例,而不是同名的单例对象。


ChecksumAccumulator.calculate("Every value is an object.")
然而单例对象不只是静态方法的收容站。它同样是个第一类的对象。因此你可以把单例对象的名字看作是贴在对象上的“名签”:


[Scala基础]-- 伴生类和伴生对象_第2张图片


定义单例对象不是定义类型(在 Scala 的抽象层次上说)。如果只是 ChecksumAccumulator对 象 的 定 义 , 你 就 建 不 了 ChecksumAccumulator 类 型 的 变 量 。

 宁 愿 这 么 说 ,ChecksumAccumulator 类型是由单例对象的伴生类定义的。然而,单例对象扩展了超类并可以混入特质。

由于每个单例对象都是超类的实例并混入了特质,你可以通过这些类型调用它的方法,用这些类型的变量指代它,并把它传递给需要这些类型的方法。
类和单例对象间的一个差别是,单例对象不带参数,而类可以。

因为你不能用 new 关键字实例化一个单例对象,你没机会传递给它参数。每个单例对象都被作为由一个静态变量指向的 虚构类: :synthetic class的一个实例来实现,因此它们与Java静态类有着相同的初始化语法。 


特别:单例对象会在第一次被访问的时候初始化。




你可能感兴趣的:(scala,Scala)