1. 访问修饰符概览
在 Java 中,类、字段、方法与构造器有四种访问级别:
| 修饰符 | 可访问范围 |
|---|---|
|
到处都可见:类内、其他类、其他包 |
|
类内、子类(继承者)、同一包中的其他类 |
|
仅在包内(当未显式指定修饰符时) |
|
仅在当前类内部 |
下面逐一分析它们——配合示例、一些小笑话和意想不到的转折。
public — 公共访问
public 就像向全世界张贴的公告:“人人可进!”。如果类、字段、方法或构造器被声明为 public,就可以从任何其他类访问,甚至来自其他包。
示例:
public class Cat {
public String name;
public void sayMeow() {
System.out.println("喵!");
}
}
该类及其字段/方法可以从任何地方访问。这在你希望类对所有人可用时很方便,例如在编写库时。
但是!公共字段并不总是好主意(见上一讲)。通常只将需要对外可见的方法设为 public,而字段几乎总是保持为 private。
private — 仅在类内部可见
private 就像带密码的保险箱:除了类自身,谁也无法访问这些成员。即使是子类也看不到私有字段和方法!
示例:
public class Cat {
private String secretName;
public void setSecretName(String name) {
secretName = name;
}
public String getSecretName() {
return secretName;
}
}
这里 secretName 无法被其他类直接读取或修改。只有 Cat 自身(或它的方法)可以这样做。这是封装的基础:我们隐藏内部细节,并只通过方法提供访问。
protected — 受保护的访问
protected 有点像 VIP 通行证:类自身、它的子类(即使在其他包中)以及当前包内的所有类都可访问。
示例:
public class Animal {
protected int age;
protected void growOlder() {
age++;
}
}
现在,任何继承自 Animal 的类都可以访问字段 age 和方法 growOlder()。
public class Cat extends Animal {
public void haveBirthday() {
growOlder();
System.out.println("猫已经 " + age + " 岁了!");
}
}
同一包中的所有类也可以访问 protected 成员。
(package-private) — 包内可见
如果你完全未指定访问修饰符,成员就被视为 package-private(也称“默认访问”)。这就像一扇没有锁的门,但只对自己人开放:仅同一包的类可访问。
示例:
class Dog {
String name; // package-private
void bark() { // package-private
System.out.println("汪!");
}
}
类 Dog、它的字段 name 以及方法 bark() 仅在同一包内可用。从其他包访问会导致编译错误。
2. 在字段与方法上应用访问修饰符
为什么字段几乎总是设为 private
类的字段代表其内部状态。如果将它们设为公共的,任何外部代码都可以随时修改它们。这就像让陌生小孩把你的家具当乐高玩:某天醒来,你会发现冰箱倒置在浴室里。
糟糕的封装示例:
public class Person {
public String name;
public int age;
}
良好封装的示例:
public class Person {
private String name;
private int age;
public void setName(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
通过方法(getter 与 setter)可以控制外部类如何修改字段。例如,你可以禁止出现负年龄。
何时将方法设为 public、protected 或 package-private
- public —— 当方法需要对所有人可见时。通常用于类的对外核心功能。
- protected —— 当方法只供子类或包内使用(例如可能在子类中有用的辅助方法)。
- package-private —— 当方法只在包内需要,但不应对外可见(例如实现细节)。
- private —— 当方法仅供类内部使用(例如内部逻辑的辅助方法)。
示例:
public class BankAccount {
private double balance;
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
protected void applyInterest() {
balance *= 1.05;
}
void internalAudit() {
// package-private: 仅供同一包内的类使用
}
private void logAction(String action) {
// 仅供类的内部使用
}
}
3. 代码示例:含不同访问级别的类
我们来创建一个包含公共、私有、受保护与包级成员的类。顺便从其他类尝试访问它们,看看会发生什么。
package zoo;
public class Animal {
public String publicName = "对所有人可见";
protected String protectedName = "仅对子类和同一包可见";
String packageName = "仅对同一包可见";
private String privateName = "仅对 Animal 可见";
public void publicMethod() {
System.out.println("公共方法");
}
protected void protectedMethod() {
System.out.println("受保护的方法");
}
void packageMethod() {
System.out.println("包级方法");
}
private void privateMethod() {
System.out.println("私有方法");
}
}
现在在同一包中的另一个类里尝试访问这些成员:
package zoo;
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // OK
System.out.println(animal.packageName); // OK
System.out.println(animal.privateName); // 错误:private
animal.publicMethod(); // OK
animal.protectedMethod(); // OK
animal.packageMethod(); // OK
animal.privateMethod(); // 错误:private
}
}
再从另一个包中尝试访问:
package other;
import zoo.Animal;
public class Test {
public static void main(String[] args) {
Animal animal = new Animal();
System.out.println(animal.publicName); // OK
System.out.println(animal.protectedName); // 错误:protected
System.out.println(animal.packageName); // 错误:package-private
System.out.println(animal.privateName); // 错误:private
animal.publicMethod(); // OK
animal.protectedMethod(); // 错误:protected
animal.packageMethod(); // 错误:package-private
animal.privateMethod(); // 错误:private
}
}
结论:
- public —— 到处都可访问。
- protected —— 在包内和子类中可访问(即使子类位于其他包,通过继承仍可访问)。
- package-private —— 仅在包内可访问。
- private —— 仅在类内部可访问。
4. 最佳实践:如何选择访问修饰符
尽量缩小可见范围
能看到你字段或方法的代码越少越好。只对外暴露确有必要的内容。这被称为最小特权原则(principle of least privilege)。
- 字段几乎总是应该是 private。例外仅限真正的常量(public static final),细节将在后续讲解。
- 只有当方法属于类的对外接口时 才将其设为 public。
- 辅助方法(内部逻辑)—— private。
- 供子类使用的方法 —— protected。
- 包内的内部服务方法 —— package-private。
为什么这很重要?
- 一旦暴露实现细节,任何改动都可能破坏他人的代码。
- 类会变得更难测试与维护。
- 偶发错误(如对字段的错误修改)可能导致缺陷。
有时新人会想:“何必这么麻烦,让一切都 public 不就好了吗!” 但当项目变大时,你可能不得不重写半个程序,只因为有人直接修改了类的字段。
5. 使用访问修饰符时的常见错误
错误 1:把字段保留为 public 或默认的 package-private。
如果不指定修饰符,字段或方法将对包内所有类可见。这可能导致意外情况——例如有人直接修改了你的字段。
错误 2:尝试从其他类访问 private 成员。
编译器不会允许——你会得到一个错误。若你试图通过 reflection(反射)绕过它——欢迎来到充满缺陷与各种崩溃的世界。
错误 3:过度使用 public。
如果把所有东西都声明为 public,这个类就像一盒裸露的电线——谁都可能随手一拉把它弄坏。
错误 4:对只给子类用的方法没有使用 protected。
如果某个方法仅用于在子类中扩展,请将其 protected,而不是 public。
错误 5:无意间成为 package-private 可见。
有时会忘记写修饰符,结果方法对整个包可见。如果你以为它是 private,这会让人措手不及。
GO TO FULL VERSION