Skip to content

类与对象 ​

学习目标 ​

  • 理解类、对象、引用、字段、方法和构造器的职责。
  • 掌握 this、静态成员和初始化顺序的运行含义。
  • 用封装和构造器不变式设计一个状态始终有效的领域对象。

核心知识点 ​

专业术语 ​

  • 类(class):描述对象状态和行为的类型声明;它本身不是某一个运行时对象。
  • 对象(object):按类创建的运行时实例,拥有身份、状态和可调用行为。
  • 引用(reference):保存对象定位信息的变量;多个引用可以指向同一个对象,也可以是 null。
  • 构造器(constructor):创建对象时执行的初始化代码,没有返回类型,可重载但不能被继承。
  • 不变式(invariant):对象在构造完成及每个公开操作后都必须成立的状态约束。

白话解释与边界 ​

类像一张规则图纸,对象是按图纸建出的实例,引用只是手里的地址卡。new 会分配对象、初始化字段并执行构造器;默认构造器只在类没有显式构造器时由编译器提供。this 保存正在执行方法的实例引用,能区分同名参数与字段;静态字段属于类而非某个实例。

构造器不变式是封装的起点:如果余额不能为负,就不能先创建一个非法账户再期待调用方修复。字段应尽量私有,公开方法负责校验状态。父子类的构造调用、初始化顺序和构造阶段动态分派属于单独专题,见构造器与初始化顺序;对象何时回收由可达性和垃圾收集决定,而不是由离开一个方法的瞬间决定。

常用用法 ​

class/new:声明类并创建对象 ​

类是类型声明,new 创建运行时实例;引用变量保存对象定位,多个引用可能指向同一个对象。

java
class User {
// 作用:类是类型声明,new 创建运行时实例;引用变量保存对象定位,多个引用可能指向同一个对象。
    String name;
}

User user = new User();
// 初始状态:user 当前为 new User()。
// 作用:类是类型声明,new 创建运行时实例。
user.name = "Ann";
// 关键变化:user.name = "Ann" 把新建实例的 name 字段更新为 Ann。
System.out.println(user.name);
// 输出:Ann

this:区分字段与参数 ​

this 保存正在执行方法的实例引用,常用于同名构造器参数、链式返回和显式调用实例方法。

java
class User {
// 作用:this 保存正在构造的 User 实例引用,可把字段 name 与同名构造器参数区分开。
// 初始状态:User 构造器收到 name="Ann"。
// 初始状态:this.name 指对象字段,右侧 name 指同名参数。
    private final String name;

    User(String name) {
        this.name = name;
    }

    String name() {
        return this.name;
    }
}

System.out.println(new User("Ann").name());
// 输出:Ann

private 字段 + 方法:封装对象状态 ​

字段不直接暴露,状态变化集中到方法中才能维护不变式;getter 也不应无条件返回可变内部对象。

java
// 语义:字段不直接暴露,状态变化集中到方法中才能维护不变式。
// 初始状态:counter 初始为 new Counter()。
class Counter {
    private int value;

    void increment() {
    // 作用:字段不直接暴露,状态变化集中到方法中才能维护不变式;getter 也不应无条件返回可变内部对象。
        value++;
    }

    int value() {
        return value;
    }
}

Counter counter = new Counter();
// 关键变化:counter 接收表达式 new Counter() 的计算结果。
// 初始状态:counter 的初始值为 new Counter()。
counter.increment();
// 关键变化:counter.increment() 将 value 从 0 增加到 1。
System.out.println(counter.value());
// 输出:1

实例方法:让行为依赖对象状态 ​

实例方法可以读取实例字段;把需要状态的行为写成静态方法会丢失对象边界。

java
// 语义:实例方法读取该实例字段,不能脱离实例状态改写为静态方法。
class Greeter {
    private final String prefix;

    Greeter(String prefix) {
        this.prefix = prefix;
    }

    String greet(String name) {
        return prefix + ", " + name;
    }
}

// 输入:new Greeter("Hello") 保存前缀 Hello,greet("Java") 返回 "Hello, Java"。
System.out.println(new Greeter("Hello").greet("Java"));
// 输出:Hello, Java

static:访问类级成员 ​

静态字段和方法属于类而不是某一个实例;初始化、常量和工具方法的组织细节见 static、final 与代码组织。

java
class Sequence {
// 作用:静态字段和方法属于类而不是某一个实例;初始化、常量和工具方法的组织细节见 [static、final 与代码组织](/courses/java/03-面向对象/07-static-final与代码组织)。
    private static int next = 1;
    // 初始状态:next 当前为 1。
    // 作用:静态字段和方法属于类而不是某一个实例。

    static int nextValue() {
        return next++;
    }
}

int first = Sequence.nextValue();
// 返回:第一次 Sequence.nextValue() 读取 next=1,并把静态 next 递增为 2。
int second = Sequence.nextValue();
// 返回:第二次 Sequence.nextValue() 读取 next=2,并把静态 next 递增为 3。
System.out.println(first + ", " + second);
// 输出:1, 2

instanceof:判断对象是否兼容目标类型 ​

需要在运行时判断对象能否安全地视为某个目标类型时使用 instanceof,频繁判断则应考虑改用多态方法。

java
Object value = "Java";
// 作用:需要在运行时判断对象能否安全地视为某个目标类型时使用 instanceof,频繁判断则应考虑改用多态方法。
// 初始状态:value 当前为 "Java"。
System.out.println(value instanceof String);
// 输出:true
// 作用:需要在运行时判断对象能否安全地视为某个目标类型时使用 instanceof,频繁判断则应考虑改用多态方法。

getClass:读取对象的精确运行时类 ​

需要获得对象的精确运行时类而不是判断类型兼容性时使用 getClass,调用前必须确保引用不为 null。

java
Object value = "Java";
// 初始状态:value 当前为 "Java"。
System.out.println(value.getClass().getSimpleName());
// 输出:String
// 作用:需要获得对象的精确运行时类而不是判断类型兼容性时使用 getClass,调用前必须确保引用不为 null。

不常用但需要知道 ​

实例初始化块:共享实例初始化片段 ​

初始化块会在每个构造器执行前运行,多个构造器共享逻辑时才考虑;复杂校验优先写进明确的构造器或私有工厂方法。

java
class Token {
    private final int value;

    {
        value = 42;
    }

    int value() {
        return value;
    }
}

// 输入:new Token() 执行实例初始化块,把 final 字段 value 设为 42。
System.out.println(new Token().value());
// 输出:42

this(...):复用当前类构造器 ​

this(...) 必须是构造器第一条语句;构造器重载、super(...) 和初始化顺序见构造器与初始化顺序。

java
class Point {
    private final int x;
    private final int y;

    Point() {
        this(0, 0);
        // 作用:通过 this(...) 复用当前类构造器。
    }

    Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    String text() {
        return x + "," + y;
    }
}

// 输入:new Point() 通过 this(0, 0) 委托构造,实例字段 x、y 均为 0。
System.out.println(new Point().text());
// 输出:0,0

继续阅读 ​

简单案例 ​

java
import java.math.BigDecimal;
import java.util.Objects;

public class AccountDemo {
    static final class BankAccount {
        private final String id;
        private BigDecimal balance;

        BankAccount(String id, BigDecimal openingBalance) {
            this.id = Objects.requireNonNull(id, "id");
            if (openingBalance == null || openingBalance.signum() < 0) {
                throw new IllegalArgumentException("opening balance must be non-negative");
            }
            this.balance = openingBalance;
        }

        void deposit(BigDecimal amount) {
            requirePositive(amount);
            balance = balance.add(amount);
        }

        boolean withdraw(BigDecimal amount) {
            requirePositive(amount);
            if (balance.compareTo(amount) < 0) {
                return false;
            }
            balance = balance.subtract(amount);
            return true;
        }

        BigDecimal balance() {
            return balance;
        }

        private static void requirePositive(BigDecimal amount) {
            if (amount == null || amount.signum() <= 0) {
                throw new IllegalArgumentException("amount must be positive");
            }
        }
    }

    public static void main(String[] args) {
        BankAccount account = new BankAccount("A-1", new BigDecimal("10.00"));
        // 初始状态:account 的 id 为 A-1,余额为 10.00。
        account.deposit(new BigDecimal("2.50"));
        // 关键变化:account.deposit(new BigDecimal("2.50")) 将余额增加到 12.50。
        // 返回:withdraw(new BigDecimal("20.00")) 因余额不足返回 false,余额仍为 12.50。
        System.out.println("withdraw=" + account.withdraw(new BigDecimal("20.00")));
        // 输出:withdraw=false
        System.out.println("balance=" + account.balance());
        // 输出:balance=12.50
    }
}

输出为 withdraw=false 和 balance=12.50。余额字段不能从类外直接赋值,构造器和方法共同保证“不为负、金额为正”的不变式;调用者只能通过公开行为改变状态。

易混点 ​

  • 类是类型声明,对象是运行时实例,引用变量不是对象本身;复制引用不会复制对象。
  • static 字段由类共享,实例字段各自独立;把实例状态写成静态会让不同账户相互污染。
  • 构造器负责建立初始不变式,不是普通方法,不能有返回类型,也不会被子类重写。
  • private 隐藏表示细节,getter 并不自动安全;返回可变内部对象仍可能泄露不变式。

课后小问 ​

  1. 为什么 BankAccount 不直接公开 balance 字段? 答案:公开字段会允许任意代码写入负数或绕过金额校验,类就无法保证不变式。 解析:封装把状态修改集中到 deposit、withdraw 等方法,未来更换存储方式也不会破坏调用者契约。

  2. 一个类没有写构造器时一定有无参构造器吗? 答案:只有在类完全没有声明任何构造器时,编译器才会生成默认无参构造器。 解析:一旦声明了带参数构造器,默认构造器就不会自动出现;需要无参创建时必须显式声明并定义它的校验策略。

本节小结 ​

  • 类描述结构与行为,对象是实例,引用保存对象定位而不是对象副本。
  • 构造器、私有字段和公开方法共同维护领域不变式。
  • 静态成员属于类,实例成员属于对象,初始化顺序决定构造阶段可见状态。
  • 设计类时优先保持高内聚,避免在构造器中调用开放的可重写方法。

快速回顾 ​

  • 能区分类、对象、引用和字段的含义。
  • 能为一个领域对象写出构造器不变式。
  • 能说明 this、static 和实例方法的差异。
  • 能指出公开可变字段和构造阶段开放调用的风险。