(一)单例对象
用 object 关键字来创建一个单例对象。单例对象在整个应用程序中只有一个实例,适合用于存放工具方法、常量或共享状态。
格式
object Main {
/*
* 伴生类 和 伴生对象
* 1. 类和对象的名字是一样的
* 2. 他们在同一个文件中
*
* 特性:可以访问对方的私有(private)属性
*/
// 伴生类
class Student {
// private 修饰的属性。在类的外部(在当前的class之外)无法访问!
private val name: String = "小花"
private var age: Int = 18
// 私有方法
private def getSecret(): String = "这是我的小秘密"
// 在伴生类中访问伴生对象的私有成员
def printSchoolInfo(): Unit = {
println(s"学校信息: ${Student.schoolName}") // 访问伴生对象的私有字段
println(s"学校地址: ${Student.getSchoolAddress()}") // 访问伴生对象的私有方法
}
}
// 伴生对象
object Student {
// 伴生对象的私有成员
private val schoolName: String = "Scala学校"
private def getSchoolAddress(): String = "北京市海淀区"
def sayHello(stu: Student): Unit = {
// 在伴生对象中,访问伴生类的私有属性!
println(s"Hello, ${stu.name}")
println(s"年龄: ${stu.age}")
println(s"秘密: ${stu.getSecret()}")
}
// 创建学生的方法
def createStudent(): Student = {
new Student()
}
// 修改学生年龄(访问私有字段)
def setAge(stu: Student, newAge: Int): Unit = {
// 因为伴生对象可以访问伴生类的私有成员
stu.age = newAge
}
def getAge(stu: Student): Int = {
stu.age
}
}
def main(args: Array[String]): Unit = {
val stu1 = Student.createStudent()
// 通过伴生对象访问伴生类的私有属性
Student.sayHello(stu1)
println("\n=== 修改年龄后 ===")
Student.setAge(stu1, 20)
Student.sayHello(stu1)
println("\n=== 在类中访问伴生对象的私有成员 ===")
stu1.printSchoolInfo()
// 下面的代码会编译错误(在非伴生关系中无法访问私有成员)
// println(stu1.name) // 错误: name 是私有的
// println(Student.schoolName) // 错误: schoolName 是私有的
}
}
1. 通过object关键字创建的是一个对象,不是一个类型。
2. 不能使用new关键字:声明单例对象的时候不可以使用new关键字。
3. 不能传递参数:单例对象无法传递参数。
访问控制对比
注意事项:
- 严格配对:伴生类和伴生对象必须同名且在同一个文件中
- 双向访问:双方都可以访问对方的私有成员
- 编译时检查:这种特殊权限是在编译时实现的
- 封装性:仍然保持了良好的封装性,只有伴生对象可以访问
这种特性让 Scala 能够实现更灵活的设计模式,同时在类型安全的前提下提供了更多的表达能力。
(二)伴生类和伴生对象
当同名的类和单例对象在同一个源码文件时,这个类称为单例对象的伴生类,对象称为类的伴生对象。、
object Person {
}
class Person{
}
代码说明:
1. 类名和对象名必须同名。
- 必须在同一个源码文件中。
Scala的伴生对象和伴生类的特点:
伴生对象和类的私有private成员可以相互访问。
(三)应用-单例模式
什么是单例模式?
就是通过技术手段,让某个类只能有一个对象实例。这样做的好处就节约资源,能更加精准地管理。
思路:使用private来修饰构造器,这样在类的外部就无法访问了。在伴生对象中提供获取这个实例的入口方法。
object Main {
/*
* 伴生类 和 伴生对象 实现 单例模式:一个类只能产生一个对象!
*/
// 1. 让构造函数变成私有的。在类的外部,就不能通过new来创建对象了
class Student private () {
// private 修饰的属性,在类的外部(在当前的class之外)无法访问!
private val name: String = "小花"
private var age: Int = 18
def sayHi(): Unit = {
println(s"我是$name, 今年$age岁")
}
def setAge(newAge: Int): Unit = {
age = newAge
}
def getName: String = name
def getAge: Int = age
}
// 2. 在伴生对象中创建类的实例,并返回
object Student {
// 单例实例
private val instance: Student = new Student()
def getInstance: Student = {
instance
}
}
def main(args: Array[String]): Unit = {
// 下面的代码会编译错误,因为构造函数是私有的
// val stu1 = new Student() // 错误: constructor Student in class Student cannot be accessed
// 正确的方式:通过伴生对象的 getInstance 方法获取单例实例
val stu1 = Student.getInstance
val stu2 = Student.getInstance
val stu3 = Student.getInstance
println(s"stu1 == stu2: ${stu1 == stu2}") // true
println(s"stu1 == stu3: ${stu1 == stu3}") // true
// 验证确实是同一个对象
println(s"stu1 hashCode: ${stu1.hashCode()}")
println(s"stu2 hashCode: ${stu2.hashCode()}")
println(s"stu3 hashCode: ${stu3.hashCode()}")
// 测试方法调用
stu1.sayHi() // 我是小花, 今年18岁
// 修改年龄
println("\n=== 修改年龄后 ===")
stu1.setAge(20)
stu2.sayHi() // 我是小花, 今年20岁 - 说明是同一个对象
stu3.sayHi() // 我是小花, 今年20岁 - 说明是同一个对象
// 验证所有引用都指向同一个对象
println(s"\nstu1年龄: ${stu1.getAge}")
println(s"stu2年龄: ${stu2.getAge}")
println(s"stu3年龄: ${stu3.getAge}")
}
}
关键要点:
- 私有构造函数:
class Student private () - 实例管理:在伴生对象中创建和管理单例实例
- 访问方法:通过
getInstance方法获取单例实例
这样实现既保持了单例模式的特性,又让代码意图更加明确。