单例对象是一种特殊的类,有且只有一个实例。和惰性变量一样,单例对象是延迟创建的,当它第一次被使用时才创建。
当对象定义于顶层时(即未包含在其他类中),单例对象只有一个实例。
当对象定义在一个类或方法中时,单例对象表现得和惰性变量一样。
定义一个单例对象
一个单例对象是就是一个值。单例对象的定义方式很像类,但是使用关键字 object:
代码语言:javascript复制object Box
下面例子中的单例对象包含一个方法:
代码语言:javascript复制package logging
object Logger {
def info(message: String): Unit = println(s"INFO: $message")
}
方法 info 可在程序中的任何地方被引用。像这样创建功能性方法是单例对象的一种常见用法。
下面让我们来看看如何在另外一个包中使用 info 方法:
代码语言:javascript复制// 因为 import 语句,方法 info 在此处是可见的。
import logging.Logger.info
class Project(name: String, daysToComplete: Int)
class Test {
val project1 = new Project("TPS Reports", 1)
val project2 = new Project("Website redesign", 5)
info("Created projects") // Prints "INFO: Created projects"
}
import语句要求被导入的标识具有一个“稳定路径”,一个单例对象由于全局唯一,所以具有稳定路径。
注意:如果一个 object 没定义在顶层而是定义在另一个类或者单例对象中,那么这个单例对象和其他类普通成员一样是“路径相关的”。这意味着有两种行为,class Milk 和 class OrangeJuice,一个类成员 object NutritionInfo “依赖”于包装它的实例,要么是牛奶要么是橙汁。 milk.NutritionInfo 则完全不同于oj.NutritionInfo。
伴生对象
Scala 里,在一个源代码文件中同时定义相同名字的 class 和 object 的用法被称为伴生(Companion)。
- Class 对象被称为伴生类,它和 Java 中的类是一样的。
- Object 对象是一个单例对象,用于保存一些静态变量或静态方法。如果用 Java ,必须要编写两个类才能实现,LogSegment 和 LogSegmentUtils。而在 Scala 中,使用伴生即可。
当一个单例对象和某个类共享一个名称时,这个单例对象称为伴生对象。 同理,这个类被称为是这个单例对象的伴生类。类和它的伴生对象可以互相访问其私有成员。使用伴生对象来定义那些在伴生类中不依赖于实例化对象而存在的成员变量或者方法。
代码语言:javascript复制import scala.math._
case class Circle(radius: Double) {
import Circle._
def area: Double = calculateArea(radius)
}
object Circle {
private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0)
}
val circle1 = Circle(5.0)
circle1.area
这里的 class Circle 有一个成员 area 是和具体的实例化对象相关的,单例对象 object Circle 包含一个方法 calculateArea ,它在每一个实例化对象中都是可见的。
伴生对象也可以包含工厂方法:
代码语言:javascript复制class Email(val username: String, val domainName: String)
object Email {
// 伴生对象 object Email 包含一个工厂方法 fromString
def fromString(emailString: String): Option[Email] = {
emailString.split('@') match {
case Array(a, b) => Some(new Email(a, b))
case _ => None
}
}
}
val scalaCenterEmail = Email.fromString("scala.center@epfl.ch")
scalaCenterEmail match {
case Some(email) => println(
s"""Registered an email
|Username: ${email.username}
|Domain name: ${email.domainName}
""")
case None => println("Error: could not parse email")
}
类和它的伴生对象必须定义在同一个源文件。如果需要在 REPL 里定义类和其伴生对象,需要将它们定义在同一行或者进入 :paste 模式。
Java 程序员的注意事项
在 Java 中 static 成员对应于 Scala 中的伴生对象的普通成员。
在 Java 代码中调用伴生对象时,伴生对象的成员会被定义成伴生类中的 static 成员。这称为静态转发。这种行为发生在当你自己没有定义一个伴生类时。
参考
- https://docs.scala-lang.org/zh-cn/tour/singleton-objects.html