权限修饰符与 Object 祖宗类 | JavaSE

权限修饰符与 Object 祖宗类

一、学习目标

学完本章,你应该能够:

  • 能够解释 Java 权限修饰符(Access Modifier)解决什么问题。
  • 能够区分 private、缺省权限、protectedpublic 的访问范围。
  • 能够根据封装原则选择合理的访问权限,而不是机械地全部使用 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 知识问答

  1. 什么是权限修饰符?
  2. 权限修饰符主要限制哪些成员的访问范围?
  3. private 的访问范围是什么?
  4. 缺省权限的核心边界是什么?
  5. protected 解决了什么问题?
  6. public 的基本含义是什么?
  7. 缺省权限与 protected 在跨包继承场景有什么区别?
  8. 为什么 protected 不能简单理解成“子类随便访问”?
  9. 什么是最小必要暴露?
  10. 为什么不应该为了方便全部使用 public?
  11. package 为什么也参与访问控制?
  12. Object 在 Java 类体系中是什么角色?
  13. Java 普通类与 Object 存在什么关系?
  14. Object 是不是所有类的直接父类?
  15. 为什么 Student 可以调用 toString()
  16. Object 中有哪些常见基础方法?
  17. equals()hashCode() 为什么会在以后重点学习?
  18. getClass() 与后续哪个高级技术有关?
  19. finalize() 在现代 Java 中为什么不应该作为推荐方案?
  20. 权限控制与加密是不是同一个概念?

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() {
        // 分析这里可以直接访问哪些字段
    }
}

回答:

  1. a 能否直接访问?
  2. b 能否直接访问?
  3. c 能否直接访问?
  4. d 能否直接访问?
  5. 为什么 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());
    }
}

回答:

  1. Student 是否声明了这三个方法?
  2. 为什么仍然可以调用?
  3. 它们最终来自哪个类?
  4. 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);
    }
}

要求:

  1. 判断错误来自哪一种访问权限。
  2. 说明为什么不能直接访问。
  3. 设计更加合理的对象 API,而不是简单把 password 改成 public。

下面设计是否合理?

public class OrderService {

    public void createOrder() {
    }

    public void validateInternalData() {
    }

    public void saveInternalState() {
    }

    public void calculateInternalPrice() {
    }
}

已知后三个方法完全属于内部实现。

要求:

  1. 判断当前权限设计存在什么问题。
  2. 根据最小必要暴露原则修改。
  3. 解释修改后的好处。

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

却无法根据真实类设计判断一个成员应该暴露到哪里,那么权限修饰符这一章仍然没有真正掌握。