Java 访问修饰符:public、protected、private讲解

前言

Java 提供的四种访问修饰符(private默认(包私有)protectedpublic)。


一、四种访问级别总览

1.1 一张表总结

Java 共有四种访问级别,其中三种有关键字,一种是默认(不写任何修饰符):

访问级别 关键字 可访问范围
私有 private 仅本类内部
默认(包私有) (不写) 本类 + 同包的类
受保护 protected 本类 + 同包的类 + 子类(跨包也可)
公开 public 任何地方

1.2 可见性矩阵

访问位置 private 默认 protected public
本类
同包其他类
不同包子类
不同包无关类

二、private :封装的基石

2.1 核心作用

private 是封装的直接体现:把实现细节藏起来,只暴露必要的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class BankAccount {

private double balance; // 余额不能被外部直接修改

// 通过方法控制修改逻辑,可以加校验
public void deposit(double amount) {
if (amount <= 0) throw new IllegalArgumentException("存款金额必须大于0");
this.balance += amount;
}

public void withdraw(double amount) {
if (amount > balance) throw new IllegalArgumentException("余额不足");
this.balance -= amount;
}

public double getBalance() {
return balance;
}
}

如果 balancepublic,任何地方都能 account.balance = -9999,安全性差。

2.2 private 方法的用途

private 方法通常是内部的工具方法,不对外暴露:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class PasswordUtils {

public String encrypt(String rawPassword) {
String salted = addSalt(rawPassword); // 调用私有方法
return hash(salted);
}

private String addSalt(String password) { // 加盐逻辑,外部不需要知道
return password + "SALT_2025";
}

private String hash(String input) { // 哈希逻辑,外部不需要知道
return Integer.toHexString(input.hashCode());
}
}

2.3 private 用于构造方法

private 构造方法用于阻止外部直接实例化,常见于:

  • 单例模式:控制只有一个实例
  • 工具类:不需要实例化,只有静态方法
  • 工厂方法模式:强制通过工厂方法创建对象
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 工具类:禁止实例化
public class MathUtils {
private MathUtils() {} // 私有构造,防止 new MathUtils()

public static int add(int a, int b) { return a + b; }
}

// 单例模式
public class ConfigManager {
private static final ConfigManager INSTANCE = new ConfigManager();

private ConfigManager() {} // 私有构造

public static ConfigManager getInstance() { return INSTANCE; }
}

2.4 测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class PrivateTest {

public static void main(String[] args) {
BankAccount account = new BankAccount();
account.deposit(1000);
account.withdraw(200);
System.out.println("余额:" + account.getBalance()); // 800.0

// account.balance = -9999; // 编译错误:balance has private access

try {
account.withdraw(9999);
} catch (IllegalArgumentException e) {
System.out.println("异常捕获:" + e.getMessage()); // 余额不足
}

// 工具类无法实例化(编译期报错)
// MathUtils utils = new MathUtils(); // 编译错误
System.out.println(MathUtils.add(3, 4)); // 7

// 单例
ConfigManager c1 = ConfigManager.getInstance();
ConfigManager c2 = ConfigManager.getInstance();
System.out.println(c1 == c2); // true,同一个实例
}
}

三、public :对外暴露的接口

3.1 核心作用

public 意味着承诺:这个方法或类对所有调用方开放,一旦对外公开,就要保证向后兼容,轻易不能改变签名。

1
2
3
4
// 这是一个公开的 API,改动签名会影响所有调用方
public interface PaymentService {
public PayResult pay(String orderId, double amount);
}

3.2 public 类与非 public

一个 .java 文件中只能有一个 public 类,且类名必须与文件名一致。同文件中的其他类只能是默认(包私有)访问级别:

1
2
3
4
5
6
7
8
9
// 文件名必须是 Order.java
public class Order { // public 类,对外暴露
private OrderItem item; // 使用同文件的非 public 类
}

class OrderItem { // 默认访问级别,包私有,外部模块不可见
String productName;
int quantity;
}

OrderItem 只是 Order 的实现细节,不需要对外暴露,用默认访问级别把它藏在包内部。

3.3 测试代码

1
2
3
4
5
6
7
8
// 文件:PublicTest.java
public class PublicTest {

public static void main(String[] args) {
Order order = new Order();
// OrderItem item = new OrderItem(); // 同包内可以,跨包则编译错误
}
}

四、protected :继承体系专用

4.1 核心作用

protected 是三个修饰符中最容易被误解的一个。它的设计目的是:允许子类访问父类的实现细节,同时对外部无关类保持关闭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 父类(假设在 com.animal 包)
public abstract class Animal {

private String name; // 子类也不能直接访问

protected int energy; // 子类可以直接访问,外部无关类不能访问

public Animal(String name) {
this.name = name;
this.energy = 100;
}

protected void breathe() { // 子类可以调用或重写,外部无法直接调用
energy -= 1;
System.out.println(name + " 呼吸,消耗能量");
}

public abstract void move(); // 公开抽象方法,子类必须实现
}

// 子类(在 com.animal.impl 包,跨包继承)
public class Dog extends Animal {

public Dog(String name) {
super(name);
}

@Override
public void move() {
breathe(); // ✓ 可以调用父类 protected 方法
energy -= 5; // ✓ 可以直接访问父类 protected 字段
System.out.println("狗在跑,剩余能量:" + energy);
}
}

4.2 protected 的一个重要细节

跨包的子类只能通过自身引用访问父类的 protected 成员,不能通过父类引用访问:

1
2
3
4
5
6
7
8
// 假设 Dog 和 Animal 不在同一个包
public class Dog extends Animal {

public void test(Animal otherAnimal) {
this.energy = 50; // ✓ 通过自身引用访问,合法
// otherAnimal.energy = 50; // ✗ 编译错误!不能通过父类引用访问
}
}

这个细节很多人不知道,面试中也经常考察。原因是:protected 的语义是”子类对自己继承来的部分有访问权”,而不是”子类对所有 Animal 对象都有访问权”。

4.3 测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// com.animal.Animal.java
package com.animal;

public abstract class Animal {
protected int energy = 100;
protected void breathe() { energy--; }
public abstract void move();
}

// com.animal.impl.Dog.java
package com.animal.impl;

import com.animal.Animal;

public class Dog extends Animal {
@Override
public void move() {
breathe(); // ✓ 跨包子类调用父类 protected 方法
energy -= 5; // ✓ 跨包子类访问父类 protected 字段
System.out.println("剩余能量:" + energy);
}

public void test(Animal other) {
this.energy = 80; // ✓ 通过 this
// other.energy = 80; // ✗ 编译错误
}
}

// 测试入口
public class ProtectedTest {
public static void main(String[] args) {
Dog dog = new Dog();
dog.move(); // 剩余能量:94

// dog.energy = 999; // ✗ 不同包的无关类,编译错误
// dog.breathe(); // ✗ 不同包的无关类,编译错误
}
}

五、默认访问级别(包私有)

5.1 核心作用

不写任何修饰符,就是包私有(package-private)

它的语义是:这个类/方法只属于当前包的内部实现,不对包外暴露

这是模块内聚的重要工具,把一个功能模块的内部实现类全部设为包私有,对外只暴露 public 的接口类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// com.payment 包内部

// 对外暴露的接口
public interface PaymentProcessor {
PayResult process(Order order);
}

// 包内部的实现类,外部模块不可见
class AlipayClient { // 包私有,支付宝底层客户端
String call(String url) { return "ok"; }
}

class SignatureUtils { // 包私有,签名工具
static String sign(String data) { return data + "_signed"; }
}

// 对外暴露的实现
public class AlipayProcessor implements PaymentProcessor {
private AlipayClient client = new AlipayClient(); // 内部使用

@Override
public PayResult process(Order order) {
String signed = SignatureUtils.sign(order.toString());
client.call(signed);
return new PayResult(true);
}
}

外部模块只能看到 PaymentProcessorAlipayProcessorAlipayClientSignatureUtils 完全不可见,随时可以重构而不影响外部。


总结

访问修饰符是 Java 封装性的基础工具,用一句话总结每种级别的设计意图:

修饰符 设计意图
private 这是我的实现细节,跟你无关
默认 这是我们包内部的事,外人别插手
protected 这是给子类预留的扩展点,无关类别碰
public 这是我对外的承诺,随时可以调用