Java 有哪些访问修饰符?它们的可见范围有什么区别?和封装有什么关系?
简化版
Java 有 4 种访问级别,可见范围从小到大:private(私有,只有本类可见)、默认/default(不写修饰符,包内可见,也叫 package-private)、protected(本包 + 不同包的子类可见)、public(所有地方可见)。它们控制「谁能访问这个类/字段/方法」,是封装的技术基础——封装的核心思想是「隐藏内部实现、只暴露必要的接口」,用 private 把字段藏起来、用 public 的 getter/setter 或方法暴露受控的访问,从而保护对象的内部状态、降低耦合。记忆口诀:private 只本类、default 本包、protected 本包加子类、public 全部。
详细版
4 种访问级别的可见范围:
| 修饰符 | 本类 | 同包 | 不同包子类 | 不同包非子类 |
|---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ |
| 默认(不写) | ✅ | ✅ | ❌ | ❌ |
protected | ✅ | ✅ | ✅ | ❌ |
public | ✅ | ✅ | ✅ | ✅ |
public class Account {
private double balance; // 私有:只有本类能直接访问(藏起来)
String owner; // 默认:同包可见
protected int level; // protected:同包 + 子类可见
public String bankName; // public:所有地方可见
// 通过 public 方法暴露受控的访问(封装)
public double getBalance() { return balance; }
public void deposit(double amount) {
if (amount > 0) balance += amount; // 有校验,保护内部状态
}
}
封装:用访问修饰符隐藏实现:
封装的做法:
① 字段设为 private(隐藏内部数据)
② 通过 public 的 getter/setter 或方法暴露受控访问(可加校验、可改内部实现不影响外部)
好处:
- 保护内部状态(不能被外部随意改成非法值,如余额变负数)
- 降低耦合(外部只依赖公开接口,内部实现可自由改)
- 隐藏复杂性(外部不用知道内部怎么实现的)
⚠️
protected的可见范围最容易记错——它是「本包 + 不同包的子类」,不是「只有子类」。也就是说:同一个包里的类(哪怕不是子类)能访问protected成员;不同包的类必须是子类才能访问(且是通过子类自己的引用访问,有额外限制)。另外注意:外层类只能用public或默认(不能是 private/protected),private/protected 只能修饰成员(字段、方法、内部类)和内部类。
完整版教学
一、访问修饰符解决什么:控制可见性
访问修饰符解决「谁能访问这个类/字段/方法」的问题——它是实现「信息隐藏」的语言机制:
为什么要控制可见性:
如果所有东西都是 public,任何代码都能直接读写任何字段
→ 对象的内部状态可能被外部随意破坏(余额被改成负数、状态被改乱)
→ 内部实现无法改动(一改就影响所有依赖它的代码)
→ 代码高度耦合、难维护
访问修饰符让你"分级授权":
该藏的藏(private)、该给包内的给包内(default)、该给子类的给子类(protected)、该公开的公开(public)
访问修饰符的价值是「按需授权、控制访问范围」——你能精确控制「一个成员对谁可见」,从而保护内部实现、降低耦合。这是面向对象「封装」思想的语言支撑。理解「访问修饰符控制可见性、是信息隐藏的机制」,就理解了它为什么重要——它不只是语法,而是「保护对象、降低耦合」的手段。
二、四种级别的可见范围
四种访问级别从严到宽,用「四个维度」(本类/同包/不同包子类/不同包非子类)看清:
private(最严):只有本类可见
→ 完全私有,连同包的类都访问不了,用于隐藏内部实现细节
默认/package-private(不写修饰符):本包可见
→ 同一个包里的类能访问,包外都不行;用于"包内协作"
protected:本包 + 不同包的子类可见
→ 同包类能访问,不同包必须是子类才能访问;用于"给子类扩展"
public(最宽):所有地方可见
→ 任何代码都能访问,用于公开的 API
一个易错点:默认(package-private)和 protected 都包含「同包可见」——protected 是「default + 不同包的子类」。所以从可见范围看:private ⊂ default ⊂ protected ⊂ public(严格包含)。记忆口诀:「私有本类、默认本包、protected 本包加子类、public 全部」。理解「四个级别严格递进、protected = 包内 + 跨包子类」,就分清了它们的可见范围——尤其是 protected 不是「只有子类」这个易错点。
三、private:隐藏内部实现
private 是「只有本类可见」,是封装的主力——用它把「不想让外部知道/修改的东西」藏起来:
public class Timer {
private long startTime; // 私有字段:外部不该直接碰
private void reset() { startTime = 0; } // 私有方法:内部辅助逻辑,外部不该调
public void start() { startTime = System.currentTimeMillis(); } // 公开的操作
public long elapsed() { return System.currentTimeMillis() - startTime; }
}
private 的典型用途:① 私有字段(隐藏内部数据,外部只能通过公开方法访问);② 私有辅助方法(内部实现的细节步骤,不对外暴露);③ 私有构造器(如单例模式,禁止外部 new)。它的价值是「把内部实现完全藏起来」——外部看不到、改不了,所以你能自由修改内部(改字段、改私有方法)而不影响任何外部代码。这是「降低耦合、便于重构」的关键。理解「private 隐藏内部实现、是封装主力、便于重构」,就知道为什么「字段一般设 private」是最佳实践。
四、protected:给子类扩展
protected 是「本包 + 不同包的子类可见」,专门用于「给子类提供可扩展的钩子」:
public class Shape {
protected String name; // 子类能访问和用
protected void beforeDraw() { } // protected 钩子方法,子类可重写
public final void draw() { // public 模板方法
beforeDraw(); // 调用可被子类扩展的钩子
// ... 画图
}
}
class Circle extends Shape {
protected void beforeDraw() { // 子类重写 protected 钩子
this.name = "圆"; // 子类能访问父类的 protected 字段
}
}
protected 的定位是「对外隐藏、对子类开放」——它比 private 宽(子类能用)、比 public 严(非子类的外部不能用)。典型用途是模板方法模式的钩子(父类定义 protected 的可扩展点、子类重写)、给子类用的父类工具方法/字段。它体现了「为继承而设计」——父类想让子类能扩展某些内部,但又不想完全公开给所有人,就用 protected。理解「protected 对子类开放对外隐藏、用于继承扩展」,就知道它和 private/public 的区别——它是「继承体系内的可见性」。
五、封装:访问修饰符的目的
访问修饰符是手段,封装(Encapsulation) 是目的——面向对象三大特性之一,核心是「隐藏内部实现、只暴露必要的接口」:
封装的标准做法:
① 字段设为 private(隐藏数据)
② 提供 public 的 getter/setter 或业务方法(受控访问)
- getter:受控地读(可加逻辑,如格式化、懒加载)
- setter/业务方法:受控地写(可加校验,如金额不能为负)
对比"不封装"(字段都 public):
account.balance = -100; // 直接改,余额变负数,状态被破坏
封装后:
account.deposit(-100); // 走 public 方法,方法里校验金额 > 0,拒绝非法操作
封装的三大好处:① 保护内部状态(外部不能绕过校验直接改字段,如余额不会被改成负数);② 降低耦合(外部只依赖公开接口,内部实现可自由改——把 private int 改成 private long 不影响外部);③ 隐藏复杂性(外部不用知道内部怎么实现)。所以「字段私有 + 方法暴露」是封装的标准套路。理解「封装是目的、访问修饰符是手段、字段 private + 方法 public 是标准做法」,就理解了访问修饰符的意义——它服务于「保护对象、降低耦合」的封装思想。(注:不是所有字段都要机械地加 getter/setter,简单的不可变数据类如 Record 更简洁;封装的核心是「受控访问」不是「机械地加 get/set」。)
六、使用原则:最小可见性
用访问修饰符的核心原则是「最小可见性(最小权限)」——能用更严的就不用更宽的:
原则:默认用最严的,需要更宽时才放宽
字段:默认 private(几乎总是)
方法:只给内部用的 private、给包内的 default、给子类的 protected、对外 API 才 public
类:对外用 public、包内辅助类用 default
为什么最小可见性好:
① 暴露越少,外部能依赖的越少 → 内部越自由改(降低耦合)
② 暴露越少,越安全(外部无法访问不该访问的)
③ 公开的 API 是"承诺",一旦 public 就要长期兼容,别轻易 public
「最小可见性」的逻辑是「暴露是一种承诺和约束」——你 public 的东西,别人会依赖它,之后你就不能随便改(要保持兼容);而 private 的东西,别人碰不到,你可以随时改。所以「一开始就用最严的可见性,确实需要放宽时再放宽」,比「一律 public 图省事」好得多。这和「接口设计要小而精」是一个道理。理解「最小可见性原则、暴露即承诺、能严则严」,就掌握了访问修饰符的使用哲学——不是「怎么方便怎么来」,而是「暴露越少越好」。
记忆钩子:「4 种访问级别从严到宽:private(本类)⊂ default 默认不写(本包)⊂ protected(本包+跨包子类)⊂ public(全部);封装=隐藏内部只暴露接口,标准做法『字段 private + 方法 public 受控访问』(保护状态、降耦合、隐藏复杂性);用最小可见性原则——能严则严,暴露即承诺」。
七、常见误区与追问
- 误区:protected 只有子类能访问。 是「本包 + 不同包的子类」——同一个包里的类(哪怕不是子类)也能访问 protected 成员,不是「只有子类」。
- 误区:不写修饰符就是 public。 不写是「默认/package-private」(只有本包可见),不是 public;这是常见混淆,默认级别比 public 严得多。
- 误区:所有 private 字段都要加 getter/setter。 封装的核心是「受控访问」不是「机械加 get/set」——简单不可变数据用 Record 更好,该只读的不给 setter;别为了封装而盲目加。
- 误区:外层类可以用 private/protected 修饰。 外层(顶级)类只能用 public 或默认;private/protected 只能修饰成员(字段、方法)和内部类。
- 追问:4 种访问修饰符的可见范围? private(只本类)、默认(本包)、protected(本包 + 不同包的子类)、public(所有地方);可见范围严格递进 private ⊂ default ⊂ protected ⊂ public。
- 追问:封装和访问修饰符什么关系? 访问修饰符是手段、封装是目的——封装用 private 隐藏内部字段/实现、用 public 方法暴露受控访问,达到保护状态、降低耦合、隐藏复杂性的目的。
- 追问:为什么提倡最小可见性? 暴露越少外部能依赖的越少、内部越自由改(降低耦合),越安全;public 是长期兼容的承诺不能随便改,所以应「能严则严、确实需要才放宽」。
八、加强记忆
Java 有 4 种访问级别,可见范围从严到宽严格递进:private(只本类)⊂ 默认/package-private(不写修饰符,本包可见)⊂ protected(本包 + 不同包的子类)⊂ public(所有地方)。易错点:protected 是「本包 + 跨包子类」不是「只有子类」,不写修饰符是「默认/本包」不是 public。它们是封装的技术基础——封装(OOP 三大特性之一)的核心是「隐藏内部实现、只暴露必要接口」,标准做法是「字段 private + 方法 public 受控访问」(getter 受控读、setter/业务方法加校验受控写),好处是保护内部状态(外部不能绕过校验改字段)、降低耦合(外部只依赖公开接口、内部可自由改)、隐藏复杂性。private 是封装主力(隐藏内部、便于重构),protected 用于「对子类开放、对外隐藏」的继承扩展。使用遵循最小可见性原则——「能严则严、暴露即承诺」(public 是长期兼容的约束,暴露越少内部越自由)。一句话「private 本类/default 本包/protected 本包加子类/public 全部,封装靠字段私有方法公开受控访问,用最小可见性能严则严」。