权限修饰符与 Object 祖宗类 | JavaSE
权限修饰符与 Object 祖宗类
一、学习目标
学完本章,你应该能够:
- 能够解释 Java 权限修饰符(Access Modifier)解决什么问题。
- 能够区分
private、缺省权限、protected、public的访问范围。 - 能够根据封装原则选择合理的访问权限,而不是机械地全部使用
public。 - 能够解释
protected与缺省权限在继承场景中的核心区别。 - 能够理解“最小必要暴露”的代码设计思想。
- 能够解释为什么
Object是 Java 类继承体系的根类。 - 能够认识
Object提供的toString()、equals()、hashCode()、getClass()等基础方法。 - 能够解释为什么普通对象可以直接调用
toString()。 - 能够画出简单的 Java 类继承体系。
二、核心知识
2.1 为什么需要权限修饰符
上一章已经学习继承:
public class People {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
子类:
public class Teacher extends People {
}
这里已经出现两种不同权限:
private
public
为什么需要它们?
因为程序中的成员并不是:
定义以后所有代码都应该随便访问。
例如:
private String password;
通常不应该允许任意其他类直接访问。
而:
public void login() {
}
又可能本来就是提供给外部使用的公开能力。
所以 Java 需要一套机制来描述:
一个类、字段、方法或者构造器到底允许哪些代码访问。
这就是访问控制(Access Control)。
其中重要的语法工具就是:
权限修饰符(Access Modifier)。
2.2 Java 常见四种成员访问级别
Java 类成员常见的访问级别包括:
private
缺省(package-private)
protected
public
其中“缺省”不是一个关键字。
例如:
void test() {
}
方法前面没有:
private
protected
public
这就是包访问权限,也经常称为:
default access
package-private
package access
为了避免和 default 关键字混淆,本教程优先称为:
缺省权限 / 包访问权限(package-private)
2.3 四种权限的基础范围
初学阶段首先建立下面这张表:
| 权限 | 当前类 | 同一个包 | 不同包中的子类 | 其他不同包类 |
| ----------- | -----: | -------: | -------------: | -----------: |
| private | √ | × | × | × |
| 缺省 | √ | √ | × | × |
| protected | √ | √ | √* | × |
| public | √ | √ | √ | √ |
注意:
protected → 不同包子类可访问
后面还有一条很重要的细节规则,所以表中标记了 √*。
先掌握总体范围,再理解细节。
可以建立一个从严格到宽松的基本认知:
private
↓
缺省
↓
protected
↓
public
但这张“大小关系”主要用于帮助初学者建立直觉。
真正使用时应该根据:
包
继承关系
访问位置
具体判断。
2.4 private:只允许当前类直接访问
例如:
package com.yulanlin.domain;
public class User {
private String password;
private void checkPassword() {
System.out.println("检查密码");
}
}
在 User 类内部:
public void login() {
checkPassword();
System.out.println(password);
}
可以直接访问。
但是另一个类:
User user = new User();
// user.password = "123456";
// user.checkPassword();
无法直接访问。
private 最核心的语义就是:
这个成员属于当前类的内部实现。
2.5 缺省权限:同包可访问
例如:
package com.yulanlin.service;
public class UserService {
void checkUser() {
System.out.println("check");
}
}
同一个包:
package com.yulanlin.service;
public class LoginService {
public void login() {
UserService service = new UserService();
service.checkUser();
}
}
可以访问。
但是另一个包:
package com.yulanlin.controller;
通常不能直接访问:
service.checkUser();
缺省权限可以理解为:
这是当前 package 内部共享的成员,但并没有向整个程序公开。
2.6 public:公开访问
例如:
public class UserService {
public void login() {
System.out.println("login");
}
}
只要类本身以及所在模块、包等条件允许被访问,其他代码就可以使用这个公开成员。
初学阶段可以简单理解:
public是四种常见成员访问级别中开放范围最大的。
但是:
能写
public,不代表所有成员都应该写public。
如果所有东西都是:
public
那么封装就会失去大量意义。
2.7 protected:为包和继承体系提供访问能力
例如:
package com.yulanlin.parent;
public class Animal {
protected String name;
protected void eat() {
System.out.println("eat");
}
}
子类位于另一个包:
package com.yulanlin.child;
import com.yulanlin.parent.Animal;
public class Dog extends Animal {
public void show() {
System.out.println(name);
eat();
}
}
这里可以直接访问:
name
eat()
这体现了 protected 的重要意义:
父类可以向子类开放一部分能力,同时不把它完全开放给任意代码。
2.8 protected 与缺省权限最大的区别
假设父类和子类位于不同包。
缺省成员:
int age;
不同包的子类不能直接访问。
而:
protected int age;
可以在符合 Java protected 规则的子类上下文中访问。
所以二者关键区别之一就是:
缺省:
主要围绕“同一个包”
protected:
既允许同包访问,
又为跨包继承提供受保护访问
这也是 protected 这个名称的意义之一。
2.9 protected 跨包访问的一个重要细节
初学表格通常写:
protected → 不同包子类可访问
这句话本身没有错,但容易被理解得过于宽泛。
例如:
package parent;
public class Animal {
protected int age = 10;
}
不同包的子类:
package child;
import parent.Animal;
public class Dog extends Animal {
public void test() {
System.out.println(this.age);
}
}
这是合法的。
但不能简单理解成:
Dog 既然是 Animal 的子类,因此 Dog 中可以通过任何 Animal 对象随意访问 protected 成员。
例如不同包环境中这种思维就是错误的:
Animal animal = new Animal();
// System.out.println(animal.age);
protected 在跨包子类场景下存在更加严格的继承访问约束。
现阶段最重要的是掌握:
跨包 protected 访问必须与当前子类继承关系相关,而不是获得了对所有父类对象 protected 成员的任意访问权。
这比简单背一句:
protected = 子类可访问
更加准确。
2.10 权限修饰符真正解决的是“边界”
假设:
public class BankAccount {
public double balance;
}
任何代码都可以:
account.balance = -999999;
设计非常危险。
改成:
public class BankAccount {
private double balance;
public double getBalance() {
return balance;
}
public void deposit(double money) {
if (money > 0) {
balance += money;
}
}
}
这里形成:
private
↓
隐藏内部状态
public
↓
暴露合法业务能力
这正是权限控制与封装协作的结果。
2.11 最小必要暴露原则
权限设计可以遵循一个很实用的原则:
如果一个成员没有必要被更大范围访问,就不要给它更大的权限。
例如:
public class OrderService {
public void createOrder() {
validate();
calculatePrice();
save();
}
private void validate() {
}
private void calculatePrice() {
}
private void save() {
}
}
外部真正需要的是:
createOrder()
至于:
validate()
calculatePrice()
save()
可能只是内部步骤。
没必要全部:
public
这种思想可以称为:
最小必要暴露(Minimum Necessary Exposure)
它有利于:
- 降低类之间耦合;
- 隐藏内部实现;
- 限制错误调用;
- 让类的公开 API 更清晰;
- 为以后修改内部代码留下空间。
2.12 权限不是越大越好
初学者有时喜欢:
public String name;
public int age;
public double score;
public void method1() {
}
public void method2() {
}
public void method3() {
}
因为:
这样什么地方都能访问,不容易报错。
但从软件设计角度:
“什么都能访问”往往恰恰意味着边界设计不足。
好的类应该让使用者清楚看到:
哪些是对外 API
哪些是内部实现
哪些只服务于包
哪些是为继承体系准备的
2.13 Object 是什么
上一章已经看到:
class A {
}
class B extends A {
}
class C extends B {
}
那么:
A 的父类是谁?
如果 A 没有显式:
extends Xxx
Java 类仍然不是凭空存在的。
Java 中:
java.lang.Object是类层次结构的根类(root of the class hierarchy)。
例如:
public class Student {
}
可以在当前学习阶段理解成:
Object
↑
Student
再例如:
class People {
}
class Teacher extends People {
}
继承体系是:
Object
↑
People
↑
Teacher
所以原课程把 Object 称为:
祖宗类
这是一种很直观的教学叫法。
更专业的表达是:
Object是 Java 类继承体系的根类。
2.14 直接继承 Object 与间接继承 Object
情况一:
class Student {
}
没有显式父类。
Student 最终直接处于 Object 之下。
情况二:
class People {
}
class Student extends People {
}
这里:
Student
直接父类是:
People
但是继承链仍然最终到达:
Object
即:
Object
↑
People
↑
Student
所以可以说:
Java 普通类要么直接以 Object 为父类,要么通过继承链间接位于 Object 之下。
2.15 为什么 Student 可以调用 toString()
例如:
public class Student {
}
代码:
Student student = new Student();
String text = student.toString();
System.out.println(text);
Student 中明明没有定义:
toString()
为什么可以调用?
因为:
Student
↑
Object
Object 本身声明了:
public String toString()
因此 Student 对象可以使用继承得到的这一方法。
这也是理解 Object 最直观的方式:
许多看起来“任何对象都能使用”的基础能力,其源头就在 Object。
2.16 Object 中常见的方法
JDK 21 的 java.lang.Object 中包含多种基础实例方法。
当前阶段重点建立认知:
toString()
equals(Object obj)
hashCode()
getClass()
此外还有:
clone()
wait()
notify()
notifyAll()
等。
不过这些方法不应该在本章全部深入学习。
原因是它们分别关联不同后续主题。
例如:
equals / hashCode
将在集合框架之前专门深入学习对象相等与哈希规则。
而:
wait / notify / notifyAll
与多线程同步有关。
所以本章只建立:
Object 提供所有对象都具有的一批基础能力。
2.17 toString() 是什么
Object 中声明:
public String toString()
作用是:
返回对象的字符串表示。
例如:
Student student = new Student();
System.out.println(student.toString());
如果 Student 没有重写 toString(),默认结果通常类似:
com.yulanlin.Student@3f99bd52
可以粗略观察到:
类名
@
十六进制形式的哈希相关值
这种默认字符串对于 JVM 来说合法,但对于用户阅读对象内容通常并不友好。
例如我们更希望:
Student{name='张三', age=20}
如何改变 toString() 的结果?
这就需要:
方法重写(Method Overriding)
也就是下一章的核心内容。
2.18 equals() 和 hashCode() 本章只建立认知
Object 还提供:
public boolean equals(Object obj)
以及:
public int hashCode()
它们与:
- 对象相等;
- 哈希结构;
- HashSet;
- HashMap;
- 对象去重;
存在非常重要的关系。
但冻结课程已经安排:
05-10 hashCode、equals 与对象去重机制
所以本章只认识方法名称与基本定位,不提前完整展开规则。
2.19 getClass()
所有对象还可以:
student.getClass()
获得对象运行时类对应的 Class 对象。
例如:
Student student = new Student();
System.out.println(student.getClass());
Class 是以后 Java 反射体系最核心的入口之一。
冻结课程会在:
10-03 反射概述与 Class 对象
系统学习。
所以这里也只建立认知。
三、使用方法
3.1 使用不同权限划分类 API
例如:
public class UserService {
public void register(String username, String password) {
checkUsername(username);
checkPassword(password);
saveUser(username, password);
}
private void checkUsername(String username) {
System.out.println("检查用户名");
}
private void checkPassword(String password) {
System.out.println("检查密码");
}
private void saveUser(String username, String password) {
System.out.println("保存用户");
}
}
调用者只看到真正需要的公开入口:
UserService service = new UserService();
service.register("LingXi", "123456");
而不能随意:
// service.saveUser(...);
这使:
UserService 的公开能力
更加清晰。
3.2 同包共享成员
例如:
package com.yulanlin.order;
public class OrderValidator {
void validate() {
System.out.println("validate");
}
}
同包:
package com.yulanlin.order;
public class OrderService {
public void create() {
OrderValidator validator = new OrderValidator();
validator.validate();
}
}
如果:
OrderValidator
只服务于订单包内部,就没有必要把所有内容全部暴露到整个程序。
3.3 protected 服务继承扩展
例如:
public class BaseController {
protected void printLog(String message) {
System.out.println("[LOG] " + message);
}
}
子类:
public class UserController extends BaseController {
public void register() {
printLog("register");
}
}
这里表达的是:
printLog()主要提供给继承体系及相关访问范围使用,而不是任意调用者。
3.4 Object 方法来自哪里
public class Student {
private String name;
}
测试:
public class Test {
public static void main(String[] args) {
Student student = new Student();
System.out.println(student.toString());
System.out.println(student.hashCode());
System.out.println(student.getClass());
}
}
虽然 Student 自己没有声明这些方法,但它处于 Object 继承体系中。
所以这些方法仍然可以使用。
四、原理与进阶
4.1 权限修饰符控制的是“源码访问资格”
假设:
private int age;
它不是:
加密 age
也不是:
让 JVM 中完全不存在 age
它描述的是:
Java 语言允许哪些源码位置直接使用这个成员。
这属于:
语言访问控制
与密码学、身份认证、数据库权限完全不是一回事。
4.2 package 本身就是重要设计边界
很多初学代码喜欢把所有类都放进:
com.test
一个包。
这样很难体会:
缺省权限
protected
真正的设计意义。
大型 Java 系统会通过 package 对代码进行组织。
例如:
com.yulanlin.user
com.yulanlin.order
com.yulanlin.article
com.yulanlin.common
于是权限修饰符与 package 一起形成代码边界。
所以 package 不只是:
防止类重名的文件夹。
它还参与 Java 的访问控制模型。
4.3 protected 不是“比缺省单纯大一级”
在同包场景中:
缺省
protected
都可以被同包代码访问。
真正体现 protected 特殊价值的是:
跨包继承场景。
所以理解权限时,不应该只背:
private < default < protected < public
而应该理解:
private → 类边界
package-private → 包边界
protected → 包 + 继承扩展边界
public → 公共 API
这比简单背大小关系更有工程价值。
4.4 Object 为什么如此重要
因为它让 Java 对象拥有共同的最基础协议。
例如任何普通对象都可以面对:
字符串表示
相等判断
哈希值
运行时类型
对象监视器等待/通知
建立统一入口。
后续会逐渐发现:
toString()
equals()
hashCode()
getClass()
会反复出现在:
- IDE 自动生成代码;
- 集合框架;
- Debug;
- 日志;
- 反射;
- 框架开发;
之中。
所以 Object 并不是:
“知道有这个祖宗类就结束了。”
它实际上是理解 Java 对象模型的重要入口。
4.5 finalize() 不应该作为现代 Java 主线方案
Object 历史上还有:
protected void finalize()
但在现代 Java 中它已经被弃用,并计划移除。
因此 JDK 21 教学中:
不应该把
finalize()当作资源释放或对象生命周期管理的推荐方案。
后面的 IO 章节会学习:
try-with-resources
等更合适的资源管理机制。
五、实践应用
5.1 设计一个合理的账户类
public class BankAccount {
private String accountNumber;
private double balance;
public BankAccount(String accountNumber) {
this.accountNumber = accountNumber;
}
public String getAccountNumber() {
return accountNumber;
}
public double getBalance() {
return balance;
}
public void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
public boolean withdraw(double amount) {
if (amount <= 0 || amount > balance) {
return false;
}
balance -= amount;
return true;
}
private void recordTransaction() {
System.out.println("记录交易");
}
}
这里:
balance
被严格隐藏。
公开的是:
deposit()
withdraw()
getBalance()
而内部实现方法:
recordTransaction()
继续保持 private。
这就是权限设计真正服务业务模型的方式。
5.2 从 Object 引出统一对象能力
假设:
class User {
}
class Article {
}
class Comment {
}
三者没有共同业务父类。
但是:
User
Article
Comment
仍然都处于:
Object
继承体系中。
所以它们都拥有 Object 层面的公共基础能力。
这也是 Java 能建立统一对象模型的重要原因之一。
六、常见问题
6.1 Java 权限修饰符有哪些?
成员访问控制主要学习:
private
缺省
protected
public
其中缺省不是一个关键字。
6.2 default 是不是权限修饰符?
教材经常把:
default
作为“默认权限”的简称。
但实际代码中:
void test() {
}
这里并没有写 default 关键字。
准确名称可以使用:
package-private
package access
缺省访问权限
6.3 private 能被子类直接访问吗?
不能。
例如父类:
private String name;
子类不能直接:
name = "张三";
通常需要父类提供适当方法。
6.4 protected 和 public 有什么区别?
public 面向更广泛的公开访问。
protected 主要服务:
同包访问
+
继承扩展
不等于完全公开。
6.5 protected 是不是任何子类都能通过任何父类对象访问?
不是。
尤其在跨包场景中,protected 访问还受到继承访问表达式的额外限制。
不能只背:
子类 = 什么 protected 都能随便访问
6.6 权限是不是越大越方便越好?
不是。
权限过大会增加:
- 错误访问机会;
- 类之间耦合;
- 修改成本;
- API 混乱。
应该优先满足:
最小必要暴露。
6.7 Object 是不是所有类的直接父类?
不是。
例如:
Object
↑
People
↑
Teacher
Teacher 的直接父类是:
People
Object 是其继承链上的根类。
6.8 为什么没有写 extends Object?
对于没有显式其他父类的普通类,不需要手写:
extends Object
Java 已经把它纳入 Object 继承体系。
6.9 为什么 Student 可以使用 toString()?
因为:
Student
继承了 Object 层面的可继承方法。
6.10 Object.toString() 默认输出是什么?
默认通常类似:
完整类名@十六进制字符串
它更偏 JVM/Debug 层面的对象标识表达,不一定适合作为业务信息展示。
下一章会通过方法重写改变它。
6.11 equals 和 hashCode 为什么这里不详细讲?
因为课程已经安排:
05-10
hashCode、equals 与对象去重机制
那里会结合 Set、HashSet、HashMap 等场景系统理解。
6.12 wait、notify 为什么不讲?
它们属于对象监视器和线程协作机制。
将在多线程知识体系中理解更合理。
七、练习与验收
7.1 知识问答
- 什么是权限修饰符?
- 权限修饰符主要限制哪些成员的访问范围?
private的访问范围是什么?- 缺省权限的核心边界是什么?
protected解决了什么问题?public的基本含义是什么?- 缺省权限与 protected 在跨包继承场景有什么区别?
- 为什么 protected 不能简单理解成“子类随便访问”?
- 什么是最小必要暴露?
- 为什么不应该为了方便全部使用 public?
- package 为什么也参与访问控制?
- Object 在 Java 类体系中是什么角色?
- Java 普通类与 Object 存在什么关系?
- Object 是不是所有类的直接父类?
- 为什么 Student 可以调用
toString()? - Object 中有哪些常见基础方法?
equals()和hashCode()为什么会在以后重点学习?getClass()与后续哪个高级技术有关?finalize()在现代 Java 中为什么不应该作为推荐方案?- 权限控制与加密是不是同一个概念?
7.2 代码阅读
假设:
package a;
public class Parent {
private int a = 1;
int b = 2;
protected int c = 3;
public int d = 4;
}
另一个包:
package b;
import a.Parent;
public class Child extends Parent {
public void show() {
// 分析这里可以直接访问哪些字段
}
}
回答:
a能否直接访问?b能否直接访问?c能否直接访问?d能否直接访问?- 为什么
protected与缺省权限产生了不同结果?
阅读:
class Student {
}
public class Test {
public static void main(String[] args) {
Student s = new Student();
System.out.println(s.toString());
System.out.println(s.hashCode());
System.out.println(s.getClass());
}
}
回答:
Student是否声明了这三个方法?- 为什么仍然可以调用?
- 它们最终来自哪个类?
Student在继承体系中的根类是谁?
7.3 手写代码
任务一:四种权限实验
创建:
package01.Parent
package01.SamePackage
package02.Child
package02.Other
在 Parent 中分别定义:
privateMethod()
packageMethod()
protectedMethod()
publicMethod()
分别测试:
- 当前类;
- 同包类;
- 不同包子类;
- 不同包普通类;
哪些位置可以调用。
禁止仅背表格,必须真正写代码验证。
任务二:UserService
设计:
UserService
要求:
register()对外公开;- 用户名检查只供类内部使用;
- 密码检查只供类内部使用;
- 数据保存只供类内部使用;
- 不允许为了测试方便全部 public。
任务三:Object 探索
创建:
class Student {
}
然后在测试类中调用:
toString()
hashCode()
getClass()
记录结果。
不要求解释所有底层原理,但需要说明:
为什么这些方法在 Student 没有声明的情况下依然存在?
7.4 Debug
下面代码为什么无法编译?
public class User {
private String password;
}
class Login {
public void login(User user) {
System.out.println(user.password);
}
}
要求:
- 判断错误来自哪一种访问权限。
- 说明为什么不能直接访问。
- 设计更加合理的对象 API,而不是简单把 password 改成 public。
下面设计是否合理?
public class OrderService {
public void createOrder() {
}
public void validateInternalData() {
}
public void saveInternalState() {
}
public void calculateInternalPrice() {
}
}
已知后三个方法完全属于内部实现。
要求:
- 判断当前权限设计存在什么问题。
- 根据最小必要暴露原则修改。
- 解释修改后的好处。
7.5 综合训练
设计一个简单的会员账户体系:
Member
PremiumMember
要求:
Member 包含:
username
balance
internalLevelCode
并提供合理业务方法。
要求你自行决定:
- 哪些字段 private;
- 哪些方法 public;
- 是否存在值得 protected 的扩展点;
- 哪些内部方法应该 private;
- 为什么没有必要把全部成员 public。
再说明:
如果未来 PremiumMember 位于另一个 package,protected 和缺省访问权限会产生什么区别?
7.6 本章验收
不查看资料,确认自己能够:
- [ ] 完整写出四种访问权限。
- [ ] 画出四种权限基本访问表。
- [ ] 解释 package-private,而不是误认为必须写
default。 - [ ] 区分 protected 与缺省权限。
- [ ] 解释 protected 跨包继承访问的基本限制。
- [ ] 根据需求选择 private / protected / public。
- [ ] 解释最小必要暴露。
- [ ] 解释 Object 为什么被称为祖宗类。
- [ ] 画出
Object → People → Teacher。 - [ ] 说明 Student 为什么能够调用
toString()。 - [ ] 认识 Object 的常见基础方法。
- [ ] 知道
equals/hashCode为什么不能在本章草草背完。 - [ ] 知道
getClass()将连接到后续反射。 - [ ] 知道现代 Java 不应该把
finalize()当成推荐资源释放方案。
如果你只会背:
private < default < protected < public
却无法根据真实类设计判断一个成员应该暴露到哪里,那么权限修饰符这一章仍然没有真正掌握。