Java 四个访问修饰符(Access Modifiers):private、default(包级私有)、protected 和 public
核心区别与异同(总览)
| 修饰符 | 当前类 | 同包类 | 不同包子类 | 不同包非子类 | 主要用途 |
|---|---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ | 隐藏实现细节,仅供类内部使用。 |
默认 | ✅ | ✅ | ❌ | ❌ | 包内可见,实现包级别的封装。 |
protected | ✅ | ✅ | ✅ | ❌ | 允许类自身、同包类、以及所有子类(无论是否同包)访问。 |
public | ✅ | ✅ | ✅ | ✅ | 公开接口,任何地方都可访问。 |
关键异同点:
- 可见性范围: 从上表清晰可见,
public范围最广,private范围最窄。protected介于默认和public之间,特别之处在于它向所有子类开放了访问权限(即使子类在不同包)。 - 封装性:
private提供了最强的封装,完全隐藏内部实现。默认提供了包级别的封装。protected在封装的同时,为继承体系提供了扩展点。public几乎不提供封装,暴露接口。
- 设计原则: 遵循“最小权限原则”。优先使用最严格的访问级别(通常是
private),只在确实需要更大范围访问时才放宽限制(如protected用于继承,public用于 API)。
详细解释与源码示例
1. private
- 可见性: 仅在声明它的类内部可见。同一个类的不同实例之间可以互相访问对方的
private成员。 - 原理: 编译器在编译时会检查访问权限。如果外部代码(其他类)尝试访问一个
private成员,编译器会直接报错。 - 主要用途:
- 隐藏类的内部状态(字段)。
- 隐藏仅供类内部使用的辅助方法。
- 强制通过公共方法(如 getter/setter)访问或修改数据,实现数据封装和验证。
- 源码示例:
public class BankAccount {
// private 字段:外部无法直接访问余额
private double balance;
// public 方法:外部可以通过此方法存款(封装了验证逻辑)
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
// public 方法:外部可以通过此方法查询余额
public double getBalance() {
return balance;
}
// private 方法:仅供类内部使用,例如计算利息
private double calculateInterest() {
return balance * 0.05; // 假设 5% 利息
}
// 类内部的一个方法可以调用 private 方法
public void applyInterest() {
balance += calculateInterest();
}
}
// 另一个类中
public class Main {
public static void main(String[] args) {
BankAccount account = new BankAccount();
account.deposit(1000);
System.out.println(account.getBalance()); // 正确:通过 public 方法访问
// account.balance = 2000; // 编译错误!balance 是 private
// double interest = account.calculateInterest(); // 编译错误!calculateInterest 是 private
}
}
2. 默认 (Default / Package-Private)
- 可见性: 在同一个包(package)内的所有类中可见。没有显式指定任何访问修饰符时,就是默认访问级别。
- 原理: 编译器检查访问是否发生在同一个包内。
- 主要用途:
- 实现包级别的封装。包内的类可以相互协作,但对包外的类隐藏实现细节。
- 当某个成员主要供同一个包内的其他类使用,且不需要被子类(尤其是不同包的子类)直接访问时使用。
- 源码示例:
// 文件: com/example/util/Logger.java
package com.example.util;
class Logger { // 注意:没有 public, private, protected -> 默认访问级别
void log(String message) {
System.out.println("[LOG] " + message);
}
}
// 文件: com/example/util/Service.java (同一个包 com.example.util)
package com.example.util;
public class Service {
public void doSomething() {
Logger logger = new Logger(); // 可以访问,同包
logger.log("Service is doing something");
}
}
// 文件: com/example/app/Main.java (不同包 com.example.app)
package com.example.app;
import com.example.util.Service;
public class Main {
public static void main(String[] args) {
Service service = new Service();
service.doSomething(); // 可以访问 Service 的 public 方法
// Logger logger = new Logger(); // 编译错误!Logger 是默认访问,不同包不可见
// logger.log("Hello"); // 即使能创建实例(实际上不能),log 方法也不可见
}
}
3. protected
- 可见性:
- 在声明它的类内部可见。
- 在同一个包内的所有类中可见(与默认访问相同)。
- 在不同包中,只有声明类的子类中可以访问(通过继承)。
- 重要: 在子类中访问父类的
protected成员时,该访问必须通过子类自身或子类的对象来进行(或者通过子类内部访问),不能直接通过父类的对象(如果父类对象在另一个包中)。
- 原理: 编译器检查访问是否发生在同一个包内,或者访问者是否是声明类的子类(无论是否同包)。
- 主要用途:
- 允许子类访问父类的特定成员(字段或方法),以便子类可以扩展或修改父类的行为。这是实现继承和多态的关键机制之一。
- 在同一个包内,它的效果等同于
默认访问。
- 源码示例:
// 文件: com/example/shape/Shape.java
package com.example.shape;
public class Shape {
protected int x, y; // protected 字段,子类(无论是否同包)和同包类可访问
protected void draw() { // protected 方法
System.out.println("Drawing shape at (" + x + ", " + y + ")");
}
}
// 文件: com/example/shape/Circle.java (同一个包 com.example.shape)
package com.example.shape;
public class Circle extends Shape {
private int radius;
@Override
public void draw() {
super.draw(); // 可以访问父类 (Shape) 的 protected draw() 方法 (同包)
System.out.println("Drawing circle with radius " + radius + " at (" + x + ", " + y + ")"); // 可以直接访问父类 protected 字段 x, y (同包子类)
}
}
// 文件: com/example/app/GraphicApp.java (不同包 com.example.app)
package com.example.app;
import com.example.shape.Shape;
public class GraphicApp {
public void render(Shape shape) {
// shape.x = 10; // 编译错误!在不同包的非子类中不能直接访问 protected 字段
// shape.draw(); // 编译错误!在不同包的非子类中不能直接访问 protected 方法
shape.somePublicMethod(); // 如果 Shape 有 public 方法,可以调用
}
}
// 文件: com/example/app/MyCircle.java (不同包 com.example.app,是 Shape 的子类)
package com.example.app;
import com.example.shape.Shape;
public class MyCircle extends Shape {
public void drawMyCircle() {
x = 5; // 可以访问:不同包子类访问继承来的 protected 字段 (通过自身/this)
y = 10;
draw(); // 可以访问:不同包子类访问继承来的 protected 方法 (通过自身/this)
Shape parentShape = new Shape();
// parentShape.x = 15; // 编译错误!不能通过父类引用(在另一个包)访问 protected 成员
// parentShape.draw(); // 编译错误!同上
}
}
4. public
- 可见性: 在任何地方(任何包中的任何类)都可以访问。
- 原理: 编译器不做访问限制检查(除了类本身必须是
public才能在包外被访问)。 - 主要用途:
- 定义类或接口的公共 API(应用程序编程接口)。
- 暴露那些需要被程序任何部分使用的常量、方法或构造函数。
- 声明可被任何代码实例化的类。
- 源码示例:
// 文件: com/example/util/MathUtils.java
package com.example.util;
public class MathUtils { // public 类,任何地方可访问
public static final double PI = 3.14159; // public 常量
public static int add(int a, int b) { // public 静态方法
return a + b;
}
}
// 文件: com/example/app/Main.java (不同包 com.example.app)
package com.example.app;
import com.example.util.MathUtils;
public class Main {
public static void main(String[] args) {
double circleArea = MathUtils.PI * 5 * 5; // 访问 public 常量
int sum = MathUtils.add(10, 20); // 调用 public 静态方法
System.out.println("Area: " + circleArea + ", Sum: " + sum);
}
}
总结与最佳实践
private: 你的秘密。只在类内部使用。用于隐藏实现细节,保护数据(通过 getter/setter 控制访问)。默认(无修饰符): 你的家庭(包)秘密。同一个包内的类可以共享。用于包内部的协作实现,对外部包隐藏。protected: 你的家庭(包)秘密 + 告诉你的孩子(子类)。允许同包类和所有子类访问。用于设计可扩展的类库,为子类提供特定的扩展点。public: 向全世界广播。定义你的公共合约(API)。任何代码都可以使用。
最佳实践:
- 优先使用
private: 尽可能将字段声明为private,通过公共方法提供访问(如果需要)。 - 谨慎使用
public: 只将真正构成公共 API 的部分设为public。过多的public会破坏封装,增加维护难度。 - 善用
protected支持继承: 当设计类时,如果明确希望某些成员被子类使用(即使在不同包),使用protected。 默认用于包内协作: 当一些类只在同一个包内紧密协作时,使用默认访问级别。- 类的访问修饰符: 一个
.java文件中只能有一个public类,且文件名必须与该public类名一致。其他类只能是默认访问级别(同一个包内可见)。顶级类不能是private或protected(但嵌套类可以)。
理解并正确应用这些访问修饰符是编写健壮、可维护、封装良好的 Java 代码的基础。
本文来自博客园,作者:NeoLshu,转载请注明原文链接:https://www.cnblogs.com/neolshu/p/19120607

浙公网安备 33010602011771号