一、概述

在 Scala 中,访问修饰符(Access Modifiers) 用于控制类、对象、特质(traits)及其成员(如字段和方法)的可见性和访问权限。

访问修饰符类型

修饰符 描述 默认值
private 私有成员,仅在定义它的类或对象内部可见 -
protected 保护成员,仅在定义它的类的子类中可见 -
public 公共成员,任何地方都可以访问 默认
private[x] 包级私有,在指定包内可见 -
protected[x] 保护成员,在指定包或类的子类中可见 -

二、Private 成员

private 关键字修饰,带有此标记的成员仅在包含了成员定义的类或对象内部可见,同样的规则还适用于内部类。

基本语法

class MyClass {
  private val privateField = 42
  private def privateMethod(): Unit = {
    println("This is private")
  }
}

嵌套类访问规则

在 Scala 中,private 限定符比 Java 更严格。在嵌套类情况下,外层类甚至不能访问内层类的私有成员

class Outer {
  class Inner {
    private def f(): Unit = {
      println("f")
    }
    
    class InnerMost {
      f()  // 正确!InnerMost 在 Inner 内部
    }
  }
  
  (new Inner).f()  // 错误!Outer 不在 Inner 内部
}

与 Java 的区别

场景 Java Scala
外部类访问内部类私有成员 允许 不允许
内部类的内部类访问私有成员 允许 允许
// Java 中允许这样访问:
// outer.new Inner().f()  // 在 Java 中合法

// Scala 中不允许:
// (new Inner).f()  // 在 Scala 中非法

三、Protected 成员

在 Scala 中,对保护(Protected)成员的访问比 Java 更严格。它只允许保护成员在定义了该成员的类的子类中被访问,而在 Java 中,用 protected 关键字修饰的成员,除了定义了该成员的类的子类可以访问,同一个包里的其他类也可以进行访问。【继承可以被访问】

基本语法

class Super {
  protected def f(): Unit = {
    println("f")
  }
}

class Sub extends Super {
  f()  // 正确!Sub 是 Super 的子类
}

class Other {
  (new Super).f()  // 错误!Other 不是 Super 的子类
}

与 Java 的区别

package p

class Super {
  protected def f(): Unit = println("f")
}

class Sub extends Super {
  f()  // 正确
}

class Other {
  (new Super).f()  // Scala 中错误!
}

// 在 Java 中,同包的其他类也可以访问
// class Other { (new Super).f() }  // Java 中合法

四、Public 成员

Scala 中,如果没有指定任何修饰符,则默认为 public。这样的成员在任何地方都可以被访问。

基本语法

class Outer {
  class Inner {
    def f(): Unit = println("f")  // 默认 public
    
    class InnerMost {
      f()  // 正确
    }
  }
  
  (new Inner).f()  // 正确!f() 是 public
}

最佳实践

虽然 Scala 默认是 public,但建议显式声明 public 以提高代码可读性:

class MyClass {
  public val publicField = 42  // 显式 public
  public def publicMethod(): Unit = println("Hello")
}

五、包级私有访问 (Package-Private)

Scala 还支持包级私有访问修饰符,用于限制成员只能在特定包内访问。可以通过将 private 关键字后跟一个包名来实现。

语法格式

private[x]    // 对指定包 x 及 x 的子包中的类可见
protected[x]  // 对指定包 x 及 x 的子包中的类和子类可见

含义解释

private[x] 读作:“这个成员除了对 […] 中的类或 […] 中的包中的类及它们的伴生对象可见外,对其它所有类都是 private”

使用场景

这种技巧在横跨了若干包的大型项目中非常有用,它允许你定义一些在你项目的若干子包中可见但对于项目外部的客户却始终不可见的东西。

示例

package bobsrockets {
  package navigation {
    // 对 bobsrockets 包及其子包可见
    private[bobsrockets] class Navigator {
      // 对 navigation 包及其子包可见
      protected[navigation] def useStarChart(): Unit = {
        println("Using star chart")
      }
      
      class LegOfJourney {
        // 对 Navigator 类可见
        private[Navigator] val distance = 100
      }
      
      // 仅对当前对象可见
      private[this] var speed = 200
    }
  }
  
  package launch {
    import navigation._
    
    object Vehicle {
      // 对 launch 包可见
      private[launch] val guide = new Navigator
    }
  }
}

private[this] 详解

private[this] 是 Scala 特有的修饰符,表示成员仅对当前对象可见,即只能通过 this 访问:

class Counter {
  private[this] var count = 0
  
  def increment(): Unit = {
    count += 1  // 正确
  }
  
  def compare(other: Counter): Boolean = {
    // other.count  // 错误!private[this] 不允许跨对象访问
    true
  }
}

六、伴生对象和伴生类

在 Scala 中,伴生对象(Companion Object)和伴生类(Companion Class) 可以互相访问对方的私有成员。伴生对象和伴生类是同名且在同一文件中的类和对象。

基本语法

class CompanionExample {
  private val privateField: String = "I am private in class"
  
  def showField(): Unit = {
    println(privateField)
  }
}

object CompanionExample {
  // 伴生对象可以访问类的私有成员
  def accessPrivateField(example: CompanionExample): String = {
    example.privateField
  }
}

完整示例

import scala.math._

// 伴生类
class Circle(val radius: Double) {
  import Circle._
  
  def area: Double = calculateArea(radius)
}

// 伴生对象
object Circle {
  private def calculateArea(radius: Double): Double = {
    Pi * pow(radius, 2.0)
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    val circle = new Circle(5.0)
    println(s"圆的面积: ${circle.area}")
    // 输出: 圆的面积: 78.53981633974483
  }
}

另一个示例

class X {
  import X._
  
  def blah = foo  // 访问伴生对象的私有方法
}

object X {
  private def foo = 42
}

object Main extends App {
  val x = new X
  println(x.blah)  // 输出: 42
}

七、访问修饰符对比表

修饰符 类内部 子类 同包 外部包 伴生对象
无 (public)
private
protected
private[包名]
protected[包名]
private[this]

八、完整示例代码

package com.example.accessmodifiers

// 外部类
class AccessModifiersDemo {
  // 公共成员
  def publicMethod(): String = "Public method"
  
  // 私有成员
  private def privateMethod(): String = "Private method"
  
  // 受保护成员
  protected def protectedMethod(): String = "Protected method"
  
  // 包级私有成员
  private[accessmodifiers] def packagePrivateMethod(): String = "Package private method"
  
  // 内部类
  class Inner {
    def accessAll(): String = {
      s"${publicMethod()}, ${privateMethod()}, ${protectedMethod()}, ${packagePrivateMethod()}"
    }
  }
}

// 子类
class SubClass extends AccessModifiersDemo {
  def testInheritance(): String = {
    // 可以访问继承的 protected 成员
    protectedMethod()
  }
}

// 同包类
class SamePackageClass {
  def test(): AccessModifiersDemo = {
    val obj = new AccessModifiersDemo()
    // 可以访问公共成员和包级私有成员
    obj.publicMethod()
    obj.packagePrivateMethod()
    obj.publicMethod()
  }
}

object Main {
  def main(args: Array[String]): Unit = {
    val obj = new AccessModifiersDemo()
    
    // 公共成员 - 任何地方都可以访问
    println(s"公共成员: ${obj.publicMethod()}")
    
    // 私有成员 - 仅在类内部访问
    // println(obj.privateMethod())  // 错误!
    
    // 受保护成员 - 仅在子类中访问
    // println(obj.protectedMethod())  // 错误!
    
    // 包级私有成员 - 同包内可访问
    // println(obj.packagePrivateMethod())  // 错误!在不同包中
    
    // 内部类访问测试
    val inner = obj.new Inner()
    println(s"内部类访问: ${inner.accessAll()}")
    
    // 测试伴生对象
    println(s"\n=== 伴生对象示例 ===")
    val person = new Person("Alice", 30)
    println(s"Person: ${person.name}, ${person.age}")
    println(s"伴生对象访问私有字段: ${Person.secretField}")
  }
}

// 伴生类和伴生对象示例
class Person(val name: String, val age: Int) {
  private val secretField: String = "秘密信息"
}

object Person {
  // 伴生对象可以访问伴生类的私有成员
  def secretField: String = {
    val p = new Person("Test", 0)
    p.secretField
  }
}

九、最佳实践

1. 优先使用最小可见性原则

// 推荐:尽可能使用 private
class Service {
  private[this] val cache = Map.empty  // 仅当前实例可访问
  
  private def process(): Unit = {}  // 仅类内部可访问
  
  protected[Service] def hook(): Unit = {}  // 子类可访问
  
  def publicApi(): Unit = {}  // 确实需要公开的方法
}

2. 使用伴生对象管理工厂方法和静态方法

class Database(url: String) {
  private val connection = connect(url)
}

object Database {
  // 工厂方法
  def apply(url: String): Database = new Database(url)
  
  // 静态配置
  private val defaultUrl = "jdbc:mysql://localhost:3306/test"
  
  def createDefault(): Database = new Database(defaultUrl)
}

3. 使用 private[this] 防止意外访问

class Counter {
  private[this] var count = 0  // 仅当前实例可访问
  
  def increment(): Unit = count += 1
  
  def addTo(other: Counter): Unit = {
    // other.count  // 错误!不能跨对象访问
  }
}

4. 使用包级私有实现细节封装

package mylib {
  package internal {
    // 对 mylib 包内所有类可见
    private[mylib] class InternalClass
    
    // 对 internal 包内可见
    class Helper {
      private[internal] def assist(): Unit = {}
    }
  }
  
  // 公开 API
  class PublicClass {
    // 可以使用 internal 包中的类
  }
}

十、Scala 与 Java 访问修饰符对比

Scala Java 可见性
(无) public everywhere
private private class only
protected protected subclass + package
private[包] package-private package only
- (无 equivalent) subclass only
private[this] - this only
protected[this] - this + subclass

十一、常见问题

Scala 中为什么没有 explicit public 关键字?

Scala 设计理念是简洁。默认 public 是合理的选择,因为大多数成员应该是公开的。如果需要更严格的访问控制,使用 private 或 protected。

private 和 private[this] 有什么区别?

class Foo {
  private var x = 10
  private[this] var y = 20
  
  def compare(other: Foo): Unit = {
    println(other.x)  // 正确!同一类的其他实例可以访问
    // println(other.y)  // 错误!private[this] 仅限当前实例
  }
}

如何选择访问修饰符?

场景 推荐修饰符
仅类内部使用 private
仅当前实例使用 private[this]
子类需要访问 protected
包内共享 private[包名]
完全公开 无修饰符
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐