Java 访问修饰符是什么(附: public、private、protected、无修饰符的区别详解)

在 Java 的面向对象设计中,封装(Encapsulation) 是三大基石之一。而访问修饰符(Access Modifiers)正是实现封装的“守门员”。它们严格控制着类、方法、字段和构造器的可见范围,决定了谁可以访问你的代码,谁只能望而却步。

理解 public、protected、private 以及默认(default/package-private)的区别,不仅是面试必考题,更是设计健壮、安全、易维护 API 的核心技能。今天,我们将通过多维对比、场景分析和模块化新特性,彻底掌握这四个修饰符。

一、核心区别一览表

这是最直观的对比,展示了不同修饰符在四种作用域下的可见性:

修饰符 同类 (Within Class) 同包 (Within Package) 子类 (Subclass) 全局/其他包 (Outside Package) 核心语义
private ✅ ❌ ❌ ❌ 绝对私有:仅限本类内部使用
(默认) ✅ ✅ ❌ ❌ 包级私有:仅限同包内兄弟类使用
protected ✅ ✅ ✅ ❌ 受保护:同包 + 跨包子类可见
public ✅ ✅ ✅ ✅ 完全公开:全世界皆可访问

注意:

  1. 默认修饰符:即不写任何关键字,也称为 package-private。
  2. 类的修饰符:顶层类(Top-level Class)只能用 public 或 默认,不能用 private 或 protected(内部类除外)。
  3. 子类的特殊性:protected 对子类可见,但子类只能通过继承的方式访问(即操作自己的实例或父类引用指向的子类实例),不能直接访问父类实例的 protected 成员(除非在同包下)。

二、深度解析:四大修饰符详解

1. private:最严格的封锁

  • 范围:仅在声明它的类内部可见。
  • 用途:
    • 隐藏内部实现细节(如辅助方法、内部状态变量)。
    • 强制通过公共方法(Getter/Setter)访问数据,以便进行参数校验或逻辑控制。
    • 防止子类意外覆盖关键逻辑。
  • 示例:
    public class BankAccount {
        private double balance; // 外部无法直接修改余额
    
        private void validateAmount(double amount) { // 内部辅助方法
            if (amount < 0) throw new IllegalArgumentException();
        }
    
        public void deposit(double amount) {
            validateAmount(amount); // 类内部可以调用
            balance += amount;
        }
    }
    

2. 默认修饰符(Default / Package-Private):小团体的秘密

  • 范围:在同一个包(Package) 内的所有类可见。跨包不可见,即使是子类也不行。
  • 用途:
    • 构建包内部的协作网络。一组紧密相关的类可以共享实现细节,而不暴露给外部世界。
    • 测试类与被测类放在同包下,测试类可以访问默认修饰符的方法进行白盒测试。
    • 作为“半公开”API,供框架内部组件使用,但不希望被最终用户直接调用。
  • 示例:
    // 文件:com/example/util/Helper.java
    package com.example.util;
    
    class Helper { // 默认修饰符,其他包无法看到这个类
        void doInternalWork() { ... } // 仅 com.example.util 包内可见
    }
    

3. protected:为继承而生的桥梁

  • 范围:同包内所有类 + 不同包的子类。
  • 用途:
    • 设计基类(Base Class)时,允许子类扩展或重写某些方法,但不希望这些方法被无关的外部类调用。
    • 模板方法模式(Template Method Pattern)中,钩子方法(Hook Methods)通常设为 protected。
  • 陷阱提示:
    • 如果子类在不同包中,它只能访问父类对象的 protected 成员,不能访问父类引用的其他实例的 protected 成员。
    // 包 A
    public class Parent {
        protected void show() {}
    }
    
    // 包 B
    public class Child extends Parent {
        public void test(Parent p, Child c) {
            this.show();    // ✅ 合法:访问自己的继承成员
            c.show();       // ✅ 合法:访问子类实例的继承成员
            // p.show();     // ❌ 编译错误!p 是 Parent 类型,且不在同包,无法访问
        }
    }
    

4. public:开放的接口

  • 范围:对所有类可见,无论包结构如何。
  • 用途:
    • 定义对外的 API 接口、入口方法(如 main)、常量。
    • 工具类方法、枚举值。
  • 原则:尽量缩小 public 的范围。过多的 public 成员会增加维护成本,破坏封装性。

三、类的访问修饰符特殊规则

对于顶层类(非内部类),Java 只允许两种修饰符:

  1. public:类名必须与文件名完全一致。该类可以被任何包导入和使用。
  2. 默认(无修饰符):类名可以与文件名不一致(但通常建议一致)。该类只能被同一个包内的其他类访问。常用于隐藏实现类,只暴露接口。
// 文件:MyService.java
public interface MyService { void run(); } // 公开接口

// 文件:MyServiceImpl.java (可以是默认修饰符)
class MyServiceImpl implements MyService { // 外部包看不到这个实现类
    public void run() { ... }
}

// 工厂类(在同一个包内)
public class ServiceFactory {
    public static MyService create() {
        return new MyServiceImpl(); // 包内可以实例化
    }
}

优势:外部用户只能通过 ServiceFactory 获取 MyService 接口,无法依赖具体的 MyServiceImpl 实现,实现了完美的解耦。

四、常见面试题与误区澄清

Q1: protected 和默认修饰符的区别是什么?

答:关键在于跨包的子类。

  • 默认:子类如果在不同包,不可见。
  • protected:子类即使在不同包,也可见(通过继承)。
  • 在同包内,两者效果相同。

Q2: 为什么构造函数有时设为 private?

答:

  1. 单例模式:防止外部 new 对象,强制通过 getInstance() 获取唯一实例。
  2. 工具类:如 Math, Collections,类中全是静态方法,不需要实例化,私有构造器防止被实例化。
  3. 不可实例化的基类:只希望被继承,不希望直接创建基类对象。

Q3: 接口(Interface)中的成员默认是什么修饰符?

答:

  • 字段:默认且必须是 public static final。
  • 方法:
    • Java 8 之前:默认且必须是 public abstract。
    • Java 8+:default 方法默认是 public;static 方法默认是 public。
    • 注意:接口中不能使用 private(Java 9 之前),Java 9 开始接口支持 private 方法用于复用 default 方法的代码,但对外仍不可见。

Q4: 子类能访问父类的 private 成员吗?

答:绝对不能。private 是类的边界,子类继承的是父类的非私有成员。如果子类需要访问,父类必须提供 protected 或 public 的 Getter 方法。

五、2026年视角:模块化系统(Jigsaw)的影响

自 Java 9 引入模块系统(Project Jigsaw)以来,访问控制多了一层模块级限制。

  • 传统机制:public 类只要类路径(Classpath)上有,就能被访问。
  • 模块机制:即使类是 public 的,如果它所在的包没有被模块描述符(module-info.java)中的 exports 语句导出,其他模块依然无法通过反射或直接代码访问该类。
// module-info.java
module com.myapp.core {
    exports com.myapp.api; // 只有这个包里的 public 类能被外部模块访问
    
    // com.myapp.internal 包即使有 public 类,外部模块也无法访问
}

最佳实践:
在现代 Java 开发中,应结合包访问控制和模块导出控制:

  1. 将内部实现类设为默认修饰符(包级私有)。
  2. 即使某些类是 public 的,如果不想让外部模块依赖,不要在 module-info.java 中导出其所在包。
  3. 优先使用 private 和默认修饰符,最小化 API 表面面积。

六、总结与选型指南

场景 推荐修饰符 理由
内部状态变量 private 严格封装,防止外部随意篡改
仅供同类使用的辅助方法 private 隐藏实现细节
同一包内协作的工具类/实现类 默认 限制在包内,避免污染全局命名空间
基类中供子类重写的方法 protected 允许扩展,但对外部隐藏
对外提供的 API 接口/服务 public 必要的开放性
单例模式的构造器 private 控制实例创建

黄金法则:

Always start with the most restrictive access level possible.
(始终从最严格的访问级别开始。)

先设为 private,如果同包需要再改为默认,如果子类需要再改为 protected,最后万不得已才设为 public。这能最大程度降低耦合,提高代码的安全性和可维护性。

掌握访问修饰符,就是掌握了 Java 代码的“边界感”。清晰的边界是构建大型、复杂系统的基石。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 qiqicto@qq.com 举报,一经查实,本站将立刻删除。
赞 (0)
小码农的头像小码农认证作者

相关推荐

返回顶部