文章目录
-
- 前言
- 一、四种访问级别总览
-
- [1.1 一张表总结](#1.1 一张表总结)
- [1.2 可见性矩阵](#1.2 可见性矩阵)
- [二、`private` :封装的基石](#二、
private:封装的基石) -
- [2.1 核心作用](#2.1 核心作用)
- [2.2 `private` 方法的用途](#2.2
private方法的用途) - [2.3 `private` 用于构造方法](#2.3
private用于构造方法) - [2.4 测试代码](#2.4 测试代码)
- [三、`public` :对外暴露的接口](#三、
public:对外暴露的接口) -
- [3.1 核心作用](#3.1 核心作用)
- [3.2 `public` 类与非 `public` 类](#3.2
public类与非public类) - [3.3 测试代码](#3.3 测试代码)
- [四、`protected` :继承体系专用](#四、
protected:继承体系专用) -
- [4.1 核心作用](#4.1 核心作用)
- [4.2 `protected` 的一个重要细节](#4.2
protected的一个重要细节) - [4.3 测试代码](#4.3 测试代码)
- 五、默认访问级别(包私有)
-
- [5.1 核心作用](#5.1 核心作用)
- 总结
前言
Java 提供的四种访问修饰符(private、默认(包私有)、protected、public)。
一、四种访问级别总览
1.1 一张表总结
Java 共有四种访问级别,其中三种有关键字,一种是默认(不写任何修饰符):
| 访问级别 | 关键字 | 可访问范围 |
|---|---|---|
| 私有 | private |
仅本类内部 |
| 默认(包私有) | (不写) | 本类 + 同包的类 |
| 受保护 | protected |
本类 + 同包的类 + 子类(跨包也可) |
| 公开 | public |
任何地方 |
1.2 可见性矩阵
| 访问位置 | private |
默认 | protected |
public |
|---|---|---|---|---|
| 本类 | ✓ | ✓ | ✓ | ✓ |
| 同包其他类 | ✗ | ✓ | ✓ | ✓ |
| 不同包子类 | ✗ | ✗ | ✓ | ✓ |
| 不同包无关类 | ✗ | ✗ | ✗ | ✓ |
二、private :封装的基石
2.1 核心作用
private 是封装的直接体现:把实现细节藏起来,只暴露必要的接口。
java
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 方法通常是内部的工具方法,不对外暴露:
java
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 构造方法用于阻止外部直接实例化,常见于:
- 单例模式:控制只有一个实例
- 工具类:不需要实例化,只有静态方法
- 工厂方法模式:强制通过工厂方法创建对象
java
// 工具类:禁止实例化
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 测试代码
java
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 意味着承诺:这个方法或类对所有调用方开放,一旦对外公开,就要保证向后兼容,轻易不能改变签名。
java
// 这是一个公开的 API,改动签名会影响所有调用方
public interface PaymentService {
public PayResult pay(String orderId, double amount);
}
3.2 public 类与非 public 类
一个 .java 文件中只能有一个 public 类,且类名必须与文件名一致。同文件中的其他类只能是默认(包私有)访问级别:
java
// 文件名必须是 Order.java
public class Order { // public 类,对外暴露
private OrderItem item; // 使用同文件的非 public 类
}
class OrderItem { // 默认访问级别,包私有,外部模块不可见
String productName;
int quantity;
}
OrderItem 只是 Order 的实现细节,不需要对外暴露,用默认访问级别把它藏在包内部。
3.3 测试代码
java
// 文件:PublicTest.java
public class PublicTest {
public static void main(String[] args) {
Order order = new Order();
// OrderItem item = new OrderItem(); // 同包内可以,跨包则编译错误
}
}
四、protected :继承体系专用
4.1 核心作用
protected 是三个修饰符中最容易被误解的一个。它的设计目的是:允许子类访问父类的实现细节,同时对外部无关类保持关闭。
java
// 父类(假设在 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 成员,不能通过父类引用访问:
java
// 假设 Dog 和 Animal 不在同一个包
public class Dog extends Animal {
public void test(Animal otherAnimal) {
this.energy = 50; // ✓ 通过自身引用访问,合法
// otherAnimal.energy = 50; // ✗ 编译错误!不能通过父类引用访问
}
}
这个细节很多人不知道,面试中也经常考察。原因是:protected 的语义是"子类对自己继承来的部分有访问权",而不是"子类对所有 Animal 对象都有访问权"。
4.3 测试代码
java
// 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 的接口类。
java
// 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);
}
}
外部模块只能看到 PaymentProcessor 和 AlipayProcessor,AlipayClient 和 SignatureUtils 完全不可见,随时可以重构而不影响外部。
总结
访问修饰符是 Java 封装性的基础工具,用一句话总结每种级别的设计意图:
| 修饰符 | 设计意图 |
|---|---|
private |
这是我的实现细节,跟你无关 |
| 默认 | 这是我们包内部的事,外人别插手 |
protected |
这是给子类预留的扩展点,无关类别碰 |
public |
这是我对外的承诺,随时可以调用 |