scala访问修饰符

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

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

基本访问修饰符

Scala 提供 privateprotected 和默认权限(无显式修饰符)三种基本修饰符,没有 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 仅允许子类访问,限制更严格。

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

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

最佳实践

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