Java 访问修饰符:public、protected、private讲解
前言
Java 提供的四种访问修饰符(private、默认(包私有)、protected、public)。
一、四种访问级别总览
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; } }
|
如果 balance 是 public,任何地方都能 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() {}
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());
try { account.withdraw(9999); } catch (IllegalArgumentException e) { System.out.println("异常捕获:" + e.getMessage()); }
System.out.println(MathUtils.add(3, 4));
ConfigManager c1 = ConfigManager.getInstance(); ConfigManager c2 = ConfigManager.getInstance(); System.out.println(c1 == c2); } }
|
三、public :对外暴露的接口
3.1 核心作用
public 意味着承诺:这个方法或类对所有调用方开放,一旦对外公开,就要保证向后兼容,轻易不能改变签名。
1 2 3 4
| 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
| public class Order { private OrderItem item; }
class OrderItem { String productName; int quantity; }
|
OrderItem 只是 Order 的实现细节,不需要对外暴露,用默认访问级别把它藏在包内部。
3.3 测试代码
1 2 3 4 5 6 7 8
| public class PublicTest {
public static void main(String[] args) { Order order = new Order(); } }
|
四、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
| 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(); }
public class Dog extends Animal {
public Dog(String name) { super(name); }
@Override public void move() { breathe(); energy -= 5; System.out.println("狗在跑,剩余能量:" + energy); } }
|
4.2 protected 的一个重要细节
跨包的子类只能通过自身引用访问父类的 protected 成员,不能通过父类引用访问:
1 2 3 4 5 6 7 8
| public class Dog extends Animal {
public void test(Animal otherAnimal) { this.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
| package com.animal;
public abstract class Animal { protected int energy = 100; protected void breathe() { energy--; } public abstract void move(); }
package com.animal.impl;
import com.animal.Animal;
public class Dog extends Animal { @Override public void move() { breathe(); energy -= 5; System.out.println("剩余能量:" + energy); }
public void test(Animal other) { this.energy = 80; } }
public class ProtectedTest { public static void main(String[] args) { Dog dog = new Dog(); dog.move();
} }
|
五、默认访问级别(包私有)
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
|
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); } }
|
外部模块只能看到 PaymentProcessor 和 AlipayProcessor,AlipayClient 和 SignatureUtils 完全不可见,随时可以重构而不影响外部。
总结
访问修饰符是 Java 封装性的基础工具,用一句话总结每种级别的设计意图:
| 修饰符 |
设计意图 |
private |
这是我的实现细节,跟你无关 |
| 默认 |
这是我们包内部的事,外人别插手 |
protected |
这是给子类预留的扩展点,无关类别碰 |
public |
这是我对外的承诺,随时可以调用 |