1. 简介

Scala 提供了一套灵活的访问控制机制,允许我们通过不同的访问修饰符来控制类、对象、包等作用域中成员的可见性。本文将深入探讨 Scala 中的各种访问修饰符,并分析它们在类、对象和包等不同作用域下的行为。

2. 类和对象作用域

在 Scala 中,如果一个类成员是公开的(public),则无需显式声明访问修饰符。事实上,Scala 并没有 public 关键字。当我们不指定任何修饰符时,默认就是 public,其行为与 Java 中的 public 相同。

不过,**私有成员必须显式标注为 private**:

class Rectangle(widthParam: Int, heightParam: Int, colorParam: String) {
    val width: Int = widthParam
    val height: Int = heightParam
    val color: String = colorParam
    private val numberOfVertexes: Int = 4
    //...
}

✅ 在这个例子中,numberOfVertexes 是私有成员,只能在 Rectangle 类内部访问

Scala 还提供了一种更严格的访问控制:**private[this]**,它限制成员只能被当前实例访问:

abstract class Figure {
    //...
    private[this] val code = randomUUID.toString
    def printCode: Unit = println(s"$code")
    def compareCodes(that: Figure) = {
        this.code == that.code // ❌ that.code 无法访问
    }
}

⚠️ private[this] 是最严格的访问修饰符,只允许当前实例访问该字段,即使是同一个类的其他实例也无法访问。

3. 包作用域

3.1. protected 修饰符

标记为 protected 的成员,只能在定义它们的类及其子类中访问。例如,我们将抽象类 Figure 中的 color 字段标记为 protected

abstract class Figure {
    //...
    protected val color: String
}

✅ 此时,color 字段在 Figure 类及其子类之外是不可访问的,尝试在其他地方访问会引发编译错误。

3.2. protected[packageName] 修饰符

我们还可以通过 protected[packageName] 使成员在指定包及其子包中可访问。例如:

abstract class Figure {
    //...
    protected[accessmodifiers] val color: String
}

然后在同一个包或子包中的类可以访问该字段:

class Composition(fig1: Figure, fig2: Figure) {
    val color = mixColors(fig1.color, fig2.color)
    //...
}

✅ 这种方式在大型项目中非常实用,可以控制模块之间的可见性。

3.3. protected[this] 修饰符

使用 protected[this] 修饰的成员,只能在当前实例以及子类实例中访问

abstract class Figure {
    //...
    protected[this] val lineWidth: Int
}

⚠️ 与其他 protected 成员不同,protected[this] 的访问范围更小,不允许访问其他实例中的该字段

4. 嵌套类作用域

在嵌套类中,Scala 的访问控制规则与 Java 有所不同。

假设我们有一个 Rectangle 类,其中包含一个私有布尔字段 isSquare,以及一个嵌套类 InnerFigure

class Rectangle(widthParam: Int, heightParam: Int, colorParam: String) extends Figure {
    //...
    private val isSquare = width == height
    class InnerFigure(fig: Figure) {
        private val codeInner = randomUUID.toString
        val isInsideSquare = isSquare
        def printCodeInner = print(codeInner)
    }
}

✅ 在这个例子中,外层类的私有成员 isSquare 可以被嵌套类访问,这是 Scala 与 Java 的一个关键区别。

❌ 但反过来,嵌套类中的私有成员 codeInner 不能被外层类访问

5. 伴生对象访问

Scala 中的类和伴生对象之间具有特殊的访问权限。

假设我们有一个 Star 类及其伴生对象:

class Star(val vertexes: Map[Int, (Double, Double)], val color: String) extends Figure {
    import Star._
    override val lineWidth: Int = 1
    //...    
    val x = vertexes.values.toList.map(_._1)
    override protected val rightMostPoint = x.max
    val area = areaByVertexes(vertexes)
}

object Star {
    def apply(vertexes: Map[Int, (Double, Double)], color: String) =
      new Star(vertexes, color)
    private def areaByVertexes(vertexes: Map[Int, (Double, Double)]): Double = ??? // implemented somehow
    val right = rightMostPoint // ❌ Cannot resolve symbol rightMostPoint
}

✅ 在伴生类中通过 import Star._ 可以访问伴生对象的所有成员(包括私有成员)。

❌ 但伴生对象无法访问伴生类的成员。

6. 总结

本文详细介绍了 Scala 中的各种访问修饰符,并探讨了它们在类、对象、包及嵌套类中的行为差异。与 Java 相比,Scala 提供了更灵活、更细粒度的访问控制方式,尤其在嵌套类和伴生对象的处理上,有其独特之处。

📌 所有示例代码均可在 GitHub 项目 中找到。


原始标题:Access Modifiers in Scala