62. Java 类和对象 - 访问修饰符
在 Java 中,访问修饰符用于控制类及其成员(字段、方法、内部类等)的可见性和可访问性。合理地设置访问权限不仅能保护内部实现细节,还能降低类之间的耦合度,从而提高代码的安全性和可维护性。
1. 顶层(类级别)的访问修饰符
对于顶层类(非内部类),Java 只允许使用两种访问修饰符:
-
public表示类对“全世界”都可见。任何包中的任意类都能访问该类。
-
示例:
public class MyClass { // 类内容 }
-
包级私有(package-private)
(不写修饰符)
如果不加任何修饰符,则该类只在同一包内可见,包外的类无法访问。
-
示例:
class MyClass { // 仅同包内可见 }
注意:顶层类只能使用
public或包级私有两种修饰符,不能使用private或protected。
2. 成员级别的访问修饰符
当修饰符应用于类的成员时(如字段、方法、内部类),可以使用以下四种访问修饰符:
2.1 public
特性:
成员对所有类都可见,无论是在同一包还是其他包中。-
示例:
public class Person { public String name; public void sayHello() { System.out.println("Hello, " + name); } }
2.2 private
特性:
成员只能在所属的类内部访问,外部类(包括子类)都无法直接访问。用途:
用于隐藏类的内部实现细节,实现封装。-
示例:
public class Person { private int age; // 只能在 Person 类内部访问 public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
2.3 protected
特性:
成员在同一包内可见,并且在其他包中的子类中也可访问。-
示例:
public class Person { protected String address; // 同包或子类中均可访问 }
2.4 包级私有(默认,无修饰符)
特性:
成员仅在当前包内可见,包外的类(包括子类)无法访问。-
示例:
public class Person { int score; // 包级私有,仅限同包使用 }
访问修饰符总结表
| 修饰符 | 本类(class) | 同包(package) | 同包子类 | 不同包子类(subclass) | 任何类(world) |
|---|---|---|---|---|---|
| public | 访问 | 访问 | 访问 | 访问 | 访问 |
| protected | 访问 | 访问 | 访问 | 访问 | 不访问 |
| 默认(无修饰符) | 访问 | 访问 | 访问 | 不访问 | 不访问 |
| private | 访问 | 不访问 | 不访问 | 不访问 | 不访问 |
3. 访问修饰符的影响与设计建议
3.1 访问修饰符的影响
-
对外部类使用:
当使用JDK或第三方库的类时,访问修饰符决定了你是否可以调用它的方法或访问它的字段。 -
内部设计:
在自己编写类时,合理设置字段和方法的访问级别,可以防止外部类过度依赖内部实现,从而提高灵活性并降低耦合度。
3.2 设计建议
-
最小可见原则
- 除非确有必要公开,否则尽量将成员设为私有或包级私有,保持最小的可见范围。这可以避免不必要的耦合和误用。
-
实践:通常将字段设为
private,并通过public或protected方法(如getter/setter)对外提供访问接口。
-
谨慎使用
public字段- 非常量的
public字段会使其成为公共API的一部分,后续修改将受到限制,因此应尽量避免。
- 非常量的
-
封装为核心
- 内部实现细节应尽可能隐藏,只暴露外部需要的部分,既能保护数据,也能让后续重构更灵活。
4. 示例讲解
下面以一个简单的 Person 类为例,展示如何设置不同的访问修饰符:
public class Person {
// 私有字段,只能本类访问
private String name;
private int age;
// 受保护字段:同包或子类访问
protected String address;
// 包级私有构造函数:仅在本包内可使用
Person(String name) {
this.name = name;
}
// 公共构造函数:可以从任意地方调用
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// 公共 getter/setter 方法,提供对私有字段的访问
public String getName() {
return name;
}
public void setName(String newName) {
this.name = newName;
}
public int getAge() {
return age;
}
public void setAge(int newAge) {
this.age = newAge;
}
}
在这个示例中:
-
name和age字段被声明为private,只有Person类内部可以直接访问。 -
address字段使用protected,允许同包或子类访问。 - 构造函数中,一个是
public,一个是包级私有,提供不同的对象构造方式。 - 对外仅通过
getter和setter方法来访问和修改name与age,符合封装原则。
示例 1:同一包内的调用
在同一包下的调用示例(文件:TestAccess.java):
// 文件:TestAccess.java
package com.example;
public class TestAccess {
public static void main(String[] args) {
Person p = new Person("Alice", 30);
// 通过公共方法访问和修改私有字段(间接访问)
p.setName("Alice");
p.setAge(30);
System.out.println("Name: " + p.getName());
System.out.println("Age: " + p.getAge());
// 同一包内,受保护字段和默认字段都可以直接访问
p.address = "123 Main Street";
p.phone = "555-1234";
// 直接访问私有字段会编译错误(下面的代码注释掉)
// p.name = "Bob"; // 错误:name 是 private
p.displayInfo();
}
}
运行结果将会正确输出姓名、年龄、地址和电话等信息,因为在同一包内可以直接访问受保护和默认的成员。
示例 2:跨包(子类)中的调用
假设我们在另一个包 com.other 下创建一个子类 SubPerson,继承自 com.example.Person:
// 文件:SubPerson.java
package com.other;
import com.example.Person;
public class SubPerson extends Person {
public SubPerson(String name, int age) {
super(name, age);
}
// 子类可以访问受保护字段,但不能访问包级私有字段
public void updateAddress(String newAddress) {
// 受保护字段在子类中可直接访问
this.address = newAddress;
}
public void tryAccessPhone() {
// 下面代码会编译错误,因为 phone 为包级私有(默认),在不同包中不可见
// this.phone = "555-0000"; // 编译错误
}
}
在跨包调用中,子类可以访问父类中受保护(protected)的成员,但不能访问默认(包级私有)的成员。以下是跨包调用的测试示例(文件:TestSub.java):
// 文件:TestSub.java
package com.other;
public class TestSub {
public static void main(String[] args) {
SubPerson sp = new SubPerson("Bob", 25);
sp.updateAddress("456 Other Street");
// 通过公共方法仍然可以获取私有数据
System.out.println("Name: " + sp.getName() + ", Age: " + sp.getAge());
// 无法直接访问 phone,因为 phone 是包级私有,不对 com.other 包中的类开放
// sp.phone = "555-0000"; // 编译错误
sp.displayInfo();
}
}
通过这个示例,我们可以看到:
- public 成员:无论在哪个包中都可以访问。
- protected 成员:在同一包和跨包的子类中可以访问。
- 默认(包级私有) 成员:只能在同一包内访问,跨包的子类无法直接访问。
- private 成员:只能在类内部访问,其他任何地方都无法直接访问。
小结
-
同一包内:所有
public、protected和默认(无修饰符)的成员都可以直接访问;private成员只能通过公共方法访问。 -
跨包中的子类:子类可以直接访问父类中受保护(
protected)的成员,但不能访问默认(包级私有)和private成员。 -
设计建议:遵循最小可见原则,默认使用
private;仅对外暴露必要的public或protected接口,确保封装性和代码安全性。