scala访问修饰符
Scala 访问修饰符:精细控制代码可见性
访问修饰符是面向对象编程中控制类成员(属性、方法)可见范围的核心机制。与 Java 相比,Scala 的访问修饰符设计更灵活,支持包级权限控制,且对 protected 权限的限制更严格。本文将详细解析 Scala 访问修饰符的特性、使用场景及与 Java 的差异。
基本访问修饰符
Scala 提供 private、protected 和默认权限(无显式修饰符)三种基本修饰符,没有 public 关键字—— 默认权限在很多场景下等效于 public,但底层实现和使用细节有所不同。
默认权限(无修饰符)
属性的默认权限:
底层为private,但编译器会自动生成公开的 getter(属性名())和 setter(属性名_$eq(值))方法,因此从外部看可自由访问和修改。class Cat { var name: String = _ // 默认权限 } // 测试 val cat = new Cat() cat.name = "小花" // 调用 setter 方法(合法) println(cat.name) // 调用 getter 方法(合法)方法的默认权限:
等效于public,可在任何地方调用。class Dog { def bark(): Unit = println("汪汪") // 默认权限(public) } // 测试 val dog = new Dog() dog.bark() // 合法:任何地方可调用
private(私有权限)
可见范围:仅在当前类内部及其实例的方法中可见,伴生对象也可访问(Scala 特有)。
与 Java 的区别:Java 的
private不允许外部类访问,而 Scala 允许伴生对象访问类的private成员。class 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 编译器会在编译期限制访问范围。class 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 [包名]
表示成员仅在当前类、伴生对象及指定包(包括子包)内可见。
// 定义在 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与包权限的结合。package 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并手动定义访问方法。