在面向对象编程中,访问权限控制是封装特性的核心体现,它决定了类的成员在不同作用域中的可见性。Scala 提供了灵活且精细的访问权限机制,不仅支持常见的 private、protected 和 public 级别,还通过 “伴生对象” 等特性扩展了访问控制的边界。本文结合示例代码,剖析访问权限的实现逻辑与应用场景。
一、Scala 访问权限的核心级别
Scala 继承了 Java 的访问权限体系,并在此基础上进行了优化,核心分为三个级别:
| 权限级别 | 可见范围 | 核心特点 |
|---|---|---|
private | 类内部 + 伴生对象 | 最严格的访问控制,外部完全不可见 |
protected | 类内部 + 子类 | 允许子类继承和访问,外部不可见 |
public | 任意位置 | 无访问限制,最宽松的控制 |
二、代码示例:访问权限的案例展示
2.1 完整代码
// 定义 Student 类,包含不同权限的成员
class Student(var name: String, private var age: Int, protected var weight: Int) {
// 类内部方法:可访问所有权限的成员
def say(): Unit = {
println(s"姓名:$name,年龄:$age,体重:$weight") // (1) 类内部可访问 private/protected/public
}
// private 方法:仅类内部和伴生对象可见
private def sayAge(): Unit = {
println(s"年龄:$age")
}
// protected 方法:类内部和子类可见
protected def sayWeight(): Unit = {
println(s"体重:$weight")
}
}
// Student 的伴生对象(同名 object)
object Student {
def test(student: Student): Unit = {
// 伴生对象可访问 Student 类的 private/protected 成员
println(s"伴生对象访问 - 年龄:${student.age},体重:${student.weight}") // (3) 伴生对象的特殊权限
student.sayAge() // 伴生对象可调用 private 方法
}
}
// 子类 Major 继承自 Student
class Major(name: String, age: Int, weight: Int) extends Student(name, age, weight) {
// 子类可访问父类的 protected 成员,但不能访问 private 成员
def showInfo(): Unit = {
// println(s"父类年龄:${super.age}") // 报错:private 成员子类不可见 (4)
println(s"父类体重:${super.weight}") // 正常:protected 成员子类可见
super.sayWeight() // 子类可调用父类的 protected 方法
}
}
// 主方法:测试不同作用域的访问权限
def main(args: Array[String]): Unit = {
val s1 = new Student("小花", 18, 100)
// 1. 外部访问 public 成员(默认权限)
println(s"外部访问姓名:${s1.name}") // 正常:name 是 public(var 修饰默认 public)
// 2. 外部访问 private/protected 成员(报错)
// println(s"外部访问年龄:${s1.age}") // 报错:private 成员外部不可见 (2)
// println(s"外部访问体重:${s1.weight}") // 报错:protected 成员外部不可见 (2)
// 3. 类内部方法调用(通过对象调用 public 方法,间接访问 private/protected)
s1.say() // 正常:say() 是 public 方法,内部可访问所有成员
// 4. 伴生对象访问
Student.test(s1) // 正常:伴生对象可直接访问 private/protected 成员
// 5. 子类访问
val m1 = new Major("小明", 20, 120)
m1.showInfo() // 正常:子类可访问父类 protected 成员
}
2.2 运行结果
外部访问姓名:小花
姓名:小花,年龄:18,体重:100
伴生对象访问 - 年龄:18,体重:100
年龄:18
父类体重:120
体重:120
三、核心权限行为
3.1 private:类内部与伴生对象的专属权限
private 是 Scala 中最严格的访问权限,其可见范围仅包括:
- 定义该成员的类内部
- 该类的伴生对象 注意点:
- 即使是子类,也无法访问父类的
private成员 - 外部对象不能直接访问
private成员,只能通过类提供的public方法间接访问
3.2 protected:子类继承的桥梁
protected 权限的设计目的是支持继承复用,其可见范围包括:
- 定义该成员的类内部
- 该类的所有子类
注意点:
- 子类只能访问父类的
protected成员,不能访问private成员 - 外部对象不能直接访问
protected成员,确保了父类内部状态的封装性
3.3 public:无限制的访问
Scala 中没有显式的 public 关键字,未加任何权限修饰符的成员即为 public。其可见范围是:
- 任意位置。
注意点:
var name: String中的var仅表示该属性是可变的,与访问权限无关- 若需限制外部修改,可将属性声明为
private var,并提供public的getter/setter方法。
3.4 伴生对象的特殊角色
Scala 的伴生对象是与类同名的 object,它与类共享同一个作用域,因此拥有特殊的访问权限:
- 可直接访问类的
private成员 - 可直接访问类的
protected成员 - 类也可访问伴生对象的
private成员 目的是: - 将类的 “实例方法”和 “静态方法”分离
- 允许伴生对象作为类的 “工厂” 或 “工具类”,直接操作类的内部状态,而无需通过
public方法间接访问
四、访问权限的建议
4.1 封装优先:优先使用 private
- 对于类的内部状态,优先声明为
private,仅通过public方法暴露必要的操作 - 避免将属性直接声明为
public var,防止外部代码随意篡改对象状态,破坏封装性
4.2 继承复用:合理使用 protected
- 若子类需要继承父类的某个成员,可将其声明为
protected,既保证子类的可访问性,又限制外部的直接访问 - 避免滥用
protected:若某个成员不需要被子类访问,应声明为private,减少不必要的依赖
4.3 工具逻辑:利用伴生对象
- 将类的 “静态” 工具方法放在伴生对象中,利用其特殊权限直接操作类的内部成员
- 例如:在
Student伴生对象中定义apply方法,作为实例创建的入口,隐藏new关键字的使用
object Student {
def apply(name: String, age: Int, weight: Int): Student = {
// 伴生对象可直接访问 private 构造器(若类构造器声明为 private)
new Student(name, age, weight)
}
}
// 外部创建实例时,可直接使用 Student(...),无需 new
val s2 = Student("小红", 22, 110)
五、总结
访问权限机制是封装和继承的核心保障,其核心设计思想是:
- 最小权限原则:成员的访问权限应尽可能严格,仅暴露必要的接口
- 伴生对象协同:通过伴生对象实现类的 “静态” 逻辑与实例逻辑的分离,同时保持对类内部状态的访问能力
- 继承友好:
protected权限平衡了子类的可访问性与父类的封装性