Jakob Jenkov 2018-09-16
Java 访问修饰符(Access Modifier)用于指定哪些类可以访问某个给定的类、字段、构造方法或普通方法。你可以分别为类、构造方法、字段和方法单独指定访问修饰符。
在日常交流中,Java 访问修饰符有时也被称为 Java 访问说明符(access specifiers),但正确的术语是 Java 访问修饰符。
类、字段、构造方法和方法可以使用以下四种 Java 访问修饰符之一:
privatedefault(包级私有)protectedpublic
下表总结了每种 Java 访问修饰符可应用于哪些 Java 构造元素:
| private | default | protected | public | |
|---|---|---|---|---|
| 类(Class) | ❌ | ✅ | ❌ | ✅ |
| 嵌套类(Nested Class) | ✅ | ✅ | ✅ | ✅ |
| 构造方法(Constructor) | ✅ | ✅ | ✅ | ✅ |
| 方法(Method) | ✅ | ✅ | ✅ | ✅ |
| 字段(Field) | ✅ | ✅ | ✅ | ✅ |
为类、构造方法、字段或方法分配访问修饰符,有时也被称为“将该元素标记为”某种访问级别。例如,将一个方法标记为 public,就是指为其分配了 public 访问修饰符。
private 访问修饰符
如果一个方法或变量被标记为 private(即为其分配了 private 访问修饰符),那么只有同一个类内部的代码才能访问该变量或调用该方法。子类中的代码无法访问,外部类中的代码也无法访问。
类不能被标记为 private。因为如果一个类是 private 的,那么其他任何类都无法访问它,也就无法真正使用这个类。因此,Java 不允许对顶层类使用 private 修饰符。
下面是一个将字段标记为 private 的示例:
public class Clock {
private long time = 0;
}
在这个例子中,Clock 类中的成员变量 time 被标记为 private,这意味着在 Clock 类之外的代码无法直接访问 time。
通过访问器方法访问 private 字段
字段经常被声明为 private,以控制外部对其的访问。在某些情况下,这些字段确实是完全私有的,仅在类内部使用;而在其他情况下,可以通过访问器方法(如 getter 和 setter)来间接访问这些字段。
下面是一个访问器方法的示例:
public class Clock {
private long time = 0;
public long getTime() {
return this.time;
}
public void setTime(long theTime) {
this.time = theTime;
}
}
在上面的例子中,getTime() 和 setTime() 方法可以访问 time 成员变量。这两个方法被声明为 public,意味着应用程序中的任何地方都可以调用它们。
private 构造方法
如果一个类中的构造方法被标记为 private,则表示该构造方法不能从类外部调用。不过,它仍然可以从同一个类中的其他构造方法或静态方法中调用。
下面是一个示例:
public class Clock {
private long time = 0;
private Clock(long time) {
this.time = time;
}
public Clock(long time, long timeOffset) {
this(time); // 调用 private 构造方法
this.time += timeOffset;
}
public static Clock newClock() {
return new Clock(System.currentTimeMillis());
}
}
此版本的 Clock 类包含一个 private 构造方法和一个 public 构造方法。private 构造方法既可以从 public 构造方法中调用(通过 this(time)),也可以从静态方法 newClock() 中调用。
注意:上述示例仅用于说明
private构造方法的调用方式,并不代表一种“优秀设计”。
default(包级私有)访问修饰符
默认访问修饰符是指不显式写出任何访问修饰符。此时,该类/字段/构造方法/方法可以被同一类内部以及同一包中的其他类访问。因此,默认访问修饰符有时也被称为 包级私有(package-private)访问修饰符。
如果你还不了解 Java 包(package)的概念,可以参考 Java Packages 教程。
需要注意的是:即使子类继承了父类,只要子类不在同一个包中,就无法访问父类中使用默认访问修饰符的字段或方法。
下面是一个默认访问修饰符的示例:
// Clock.java
public class Clock {
long time = 0; // 默认(包级私有)访问
}
// ClockReader.java(必须与 Clock 在同一包中)
public class ClockReader {
Clock clock = new Clock();
public long readClock() {
return clock.time; // 可以访问,因为同包
}
}
Clock 类中的 time 字段没有访问修饰符,因此它是默认(包级私有)的。只要 ClockReader 和 Clock 在同一个 Java 包中,ClockReader 就可以读取 clock.time。
protected 访问修饰符
protected 访问修饰符提供的访问权限与默认访问修饰符相同(即同包内可访问),此外还允许子类访问父类中被标记为 protected 的字段和方法,即使子类位于不同的包中。
示例:
// Clock.java
public class Clock {
protected long time = 0; // 时间(毫秒)
}
// SmartClock.java(可在不同包中)
public class SmartClock extends Clock {
public long getTimeInSeconds() {
return this.time / 1000; // 子类可以访问 protected 字段
}
}
即使 SmartClock 和 Clock 不在同一个包中,SmartClock 仍能访问父类的 time 字段,因为它是 protected 的。
public 访问修饰符
public 访问修饰符表示所有代码都可以访问该类、字段、构造方法或方法,无论访问代码位于哪个类或哪个包中。
示例:
// Clock.java
public class Clock {
public long time = 0;
}
// ClockReader.java(可在任意包中)
public class ClockReader {
Clock clock = new Clock();
public long readClock() {
return clock.time; // 任何地方都能访问
}
}
由于 time 字段是 public 的,因此无论 ClockReader 在哪个包中,都可以访问它。
类的访问修饰符
需要特别注意:类本身的访问修饰符优先级高于其内部成员(字段、方法、构造方法)的访问修饰符。
例如,如果一个类被标记为默认(包级私有)访问修饰符,那么包外的任何类都无法访问该类,包括其 public 字段、public static 方法等——因为连类本身都不可见。
另外,顶层类(top-level class)只能使用两种访问修饰符:
- 默认(包级私有)
public
不能对顶层类使用 private 或 protected。
注:嵌套类(nested class)可以使用所有四种访问修饰符。
接口的访问修饰符
Java 接口旨在为实现类提供公开可用的字段和方法规范。因此:
- 接口中不能使用
private和protected访问修饰符。 - 接口中的字段和方法默认就是
public的,即使你不写public。 - 因此,接口中也不能使用默认(包级私有)访问修饰符。
换句话说,接口中的所有成员都是隐式 public 的。
访问修饰符与继承
当你创建一个子类并重写(override)父类的方法时,子类中重写的方法不能比父类中的原方法具有更严格的访问权限。
规则如下:
- 如果父类方法是
public,子类重写方法必须也是public。 - 如果父类方法是
protected,子类重写方法可以是protected或public。 - 如果父类方法是默认(包级私有),子类重写方法可以是默认、
protected或public。
✅ 允许:扩大访问权限(例如从默认变为 public)
❌ 不允许:缩小访问权限(例如从 public 变为 protected)