在写Scala代码的时候,你可能会遇到这样的场景:想定义一个工具类,里面全是静态方法,像Java里的Math类那样,随时调用,不用new对象。Scala没有“static”关键字,那怎么办?这时候就得靠“单例对象”了。
什么是单例对象?
Scala里的单例对象是用object关键字定义的。它不依赖于类的实例,一启动就存在,全局唯一。你可以把它看作是一个“自带实例的类”,JVM里只有一份。
比如你要写个日志工具,不想每次用都new一次,直接定义成单例:
object Logger {
def info(message: String): Unit = {
println(s"[INFO] $message")
}
def error(message: String): Unit = {
println(s"[ERROR] $message")
}
}
调用的时候特别简单:
Logger.info("程序启动成功")
伴生对象:和类默契配合
如果一个object和一个class同名,并且在同一个文件里,它俩就是“伴生”。伴生对象可以访问类的私有成员,常用来放工厂方法或常量。
class Person private(val name: String)
object Person {
def apply(name: String): Person = new Person(name)
}
这样创建Person就不用new了:
val p = Person("小明") // 实际调用了Person.apply
这种写法在实际项目里很常见,尤其是集合类型,比如List(1,2,3),背后也是靠伴生对象的apply实现的。
单例对象的实际用途
除了工具类和工厂模式,单例还常用于配置管理。比如你的应用要读取端口配置,可以这样写:
object AppConfig {
val defaultPort: Int = 8080
val host: String = "localhost"
def serverUrl: String = s"http://$host:$defaultPort"
}
anywhere都能通过AppConfig.serverUrl拿到地址,改配置也只改一处,省事又安全。
再比如,你想做简单的状态统计,记录请求次数:
object RequestCounter {
private var count = 0
def increment(): Unit = count += 1
def getCount: Int = count
}
每次处理请求时调用RequestCounter.increment(),监控接口直接读取即可。
注意点
单例对象是延迟初始化的,第一次被访问时才创建。多线程下是线程安全的,Scala帮你搞定了。
但别滥用。如果对象里存了大量状态,又频繁修改,可能会影响性能或导致难以调试的问题。毕竟,全局唯一,改一处影响 everywhere。