Scala 访问修饰符:精细控制代码可见性
访问修饰符是面向对象编程中控制类成员(属性、方法)可见范围的核心机制。与 Java 相比,Scala 的访问修饰符设计更灵活,支持包级权限控制,且对 protected 权限的限制更严格。本文将详细解析 Scala 访问修饰符的特性、使用场景及与 Java 的差异。
基本访问修饰符
Scala 提供 private、protected 和默认权限(无显式修饰符)三种基本修饰符,没有 public 关键字—— 默认权限在很多场景下等效于 public,但底层实现和使用细节有所不同。
默认权限(无修饰符)
属性的默认权限:
底层为private,但编译器会自动生成公开的 getter(属性名())和 setter(属性名_$eq(值))方法,因此从外部看可自由访问和修改。1
2
3
4
5
6
7
8class Cat {
var name: String = _ // 默认权限
}
// 测试
val cat = new Cat()
cat.name = "小花" // 调用 setter 方法(合法)
println(cat.name) // 调用 getter 方法(合法)方法的默认权限:
等效于public,可在任何地方调用。1
2
3
4
5
6
7class Dog {
def bark(): Unit = println("汪汪") // 默认权限(public)
}
// 测试
val dog = new Dog()
dog.bark() // 合法:任何地方可调用
private(私有权限)
可见范围:仅在当前类内部及其实例的方法中可见,伴生对象也可访问(Scala 特有)。
与 Java 的区别:Java 的
private不允许外部类访问,而 Scala 允许伴生对象访问类的private成员。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Person {
private var age: Int = 18 // private 属性
private def grow(): Unit = { // private 方法
age += 1
}
}
// 伴生对象(与 Person 类同名,同文件)
object Person {
def getAge(p: Person): Int = p.age // 合法:伴生对象可访问 private 属性
def makeGrow(p: Person): Unit = p.grow() // 合法:伴生对象可访问 private 方法
}
// 测试
val p = new Person()
println(Person.getAge(p)) // 输出:18(通过伴生对象访问)
Person.makeGrow(p)
println(Person.getAge(p)) // 输出:19- 注意:其他类(包括子类)无法访问
private成员。
- 注意:其他类(包括子类)无法访问
protected(受保护权限)
可见范围:仅在当前类及子类中可见(同包类不可访问,这与 Java 差异显著)。
底层实现:编译为 Java 字节码后是
public,但 Scala 编译器会在编译期限制访问范围。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20class Animal {
protected var weight: Double = _ // protected 属性
}
class Bird extends Animal {
def setWeight(w: Double): Unit = {
weight = w // 合法:子类可访问父类 protected 属性
}
}
// 测试
val bird = new Bird()
bird.setWeight(0.5) // 合法:通过子类方法修改
// 错误示例:同包非子类不可访问
class Fish {
def checkWeight(a: Animal): Unit = {
// println(a.weight) // 编译错误:protected 成员仅子类可见
}
}- 与 Java 的区别:Java 的
protected允许同包类访问,而 Scala 仅允许子类访问,限制更严格。
- 与 Java 的区别:Java 的
包级访问权限(灵活的范围控制)
Scala 最具特色的访问控制机制是包级权限,通过 private[包名] 或 protected[包名] 语法,将成员的可见范围限制在指定包(及子包)内,比 Java 的包权限更灵活。
private [包名]
表示成员仅在当前类、伴生对象及指定包(包括子包)内可见。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24// 定义在 com.example.model 包中
package com.example.model {
class User {
private[example] var id: Int = _ // 允许 com.example 包及子包访问
}
}
// com.example.service 包(子包)
package com.example.service {
object UserService {
def setId(user: com.example.model.User, newId: Int): Unit = {
user.id = newId // 合法:处于 com.example 包内
}
}
}
// 外部包 com.other
package com.other {
object Test {
def checkUser(user: com.example.model.User): Unit = {
// println(user.id) // 错误:不在 com.example 包内
}
}
}
protected [包名]
表示成员在当前类、子类及指定包内可见,是
protected与包权限的结合。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package com.example {
class Device {
protected[example] def powerOn(): Unit = println("开机")
}
// 子类(同包)
class Phone extends Device {
def start(): Unit = powerOn() // 合法:子类可访问
}
// 同包非子类
object DeviceManager {
def turnOn(d: Device): Unit = {
d.powerOn() // 合法:处于 com.example 包内
}
}
}
访问修饰符对比表
| 修饰符 | 可见范围 | 与 Java 的差异点 |
|---|---|---|
| 默认权限 | 属性:通过 getter/setter 公开访问;方法:公开 | Java 默认是包内可见,Scala 更接近 public |
private |
类内部、伴生对象 | Scala 允许伴生对象访问,Java 不允许 |
protected |
仅类内部和子类 | Java 允许同包访问,Scala 限制更严格 |
private[包名] |
类、伴生对象及指定包(含子包) | Java 无此语法,需手动控制包结构 |
protected[包名] |
类、子类及指定包(含子包) | Java 无此语法,结合了 protected 和包权限 |
最佳实践
- 优先使用最小权限原则:
如无需外部访问,用private;仅允许子类访问,用protected;需包内共享,用private[包名],减少意外修改风险。 - 伴生对象的合理使用:
将类的private成员的访问逻辑放在伴生对象中,避免直接暴露实现细节。 - 包级权限的场景:
当多个类属于同一模块(如service包),且需要共享内部状态但不希望外部访问时,用private[service]。 - 避免过度使用默认权限:
默认权限的属性会生成公开的 getter/setter,可能导致对象状态被意外修改,必要时用private并手动定义访问方法。