0%

scala访问修饰符

Scala 访问修饰符:精细控制代码可见性

访问修饰符是面向对象编程中控制类成员(属性、方法)可见范围的核心机制。与 Java 相比,Scala 的访问修饰符设计更灵活,支持包级权限控制,且对 protected 权限的限制更严格。本文将详细解析 Scala 访问修饰符的特性、使用场景及与 Java 的差异。

基本访问修饰符

Scala 提供 privateprotected 和默认权限(无显式修饰符)三种基本修饰符,没有 public 关键字—— 默认权限在很多场景下等效于 public,但底层实现和使用细节有所不同。

默认权限(无修饰符)

  • 属性的默认权限
    底层为 private,但编译器会自动生成公开的 getter(属性名())和 setter(属性名_$eq(值))方法,因此从外部看可自由访问和修改。

    1
    2
    3
    4
    5
    6
    7
    8
    class Cat {
    var name: String = _ // 默认权限
    }

    // 测试
    val cat = new Cat()
    cat.name = "小花" // 调用 setter 方法(合法)
    println(cat.name) // 调用 getter 方法(合法)
  • 方法的默认权限
    等效于 public,可在任何地方调用。

    1
    2
    3
    4
    5
    6
    7
    class 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
    20
    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 编译器会在编译期限制访问范围。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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 仅允许子类访问,限制更严格。

包级访问权限(灵活的范围控制)

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
    17
    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 和包权限

最佳实践

  1. 优先使用最小权限原则
    如无需外部访问,用 private;仅允许子类访问,用 protected;需包内共享,用 private[包名],减少意外修改风险。
  2. 伴生对象的合理使用
    将类的 private 成员的访问逻辑放在伴生对象中,避免直接暴露实现细节。
  3. 包级权限的场景
    当多个类属于同一模块(如 service 包),且需要共享内部状态但不希望外部访问时,用 private[service]
  4. 避免过度使用默认权限
    默认权限的属性会生成公开的 getter/setter,可能导致对象状态被意外修改,必要时用 private 并手动定义访问方法。

欢迎关注我的其它发布渠道