Scala中的单例对象和半生类

54 阅读1分钟

(一)单例对象

用 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. 不能传递参数:单例对象无法传递参数。

访问控制对比

image.png

注意事项:

  1. 严格配对:伴生类和伴生对象必须同名且在同一个文件中
  2. 双向访问:双方都可以访问对方的私有成员
  3. 编译时检查:这种特殊权限是在编译时实现的
  4. 封装性:仍然保持了良好的封装性,只有伴生对象可以访问

这种特性让 Scala 能够实现更灵活的设计模式,同时在类型安全的前提下提供了更多的表达能力。

(二)伴生类和伴生对象

当同名的类和单例对象在同一个源码文件时,这个类称为单例对象的伴生类,对象称为类的伴生对象。、

object Person {

}

class Person{

}

代码说明:

1. 类名和对象名必须同名。

  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}")
    }
}

关键要点:

  1. 私有构造函数class Student private ()
  2. 实例管理:在伴生对象中创建和管理单例实例
  3. 访问方法:通过 getInstance 方法获取单例实例

这样实现既保持了单例模式的特性,又让代码意图更加明确。