强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

10 - OOP 基础:类、对象、构造器、this、封装

10 - OOP 基础:类、对象、构造器、this、封装

类与对象

/**
 * 学生类 —— 定义学生的属性和行为
 */
public class Student {
    // 实例变量(字段)
    private String name;
    private int age;
    private double score;

    // 无参构造器
    public Student() {
        this("未知", 0, 0.0);
    }

    // 全参构造器
    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    // Getter / Setter
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄无效: " + age);
        }
        this.age = age;
    }

    public double getScore() { return score; }
    public void setScore(double score) { this.score = score; }

    // 实例方法
    public boolean isPassed() {
        return score >= 60;
    }

    public String getGrade() {
        return switch ((int)(score / 10)) {
            case 10, 9 -> "优秀";
            case 8     -> "良好";
            case 7     -> "中等";
            case 6     -> "及格";
            default    -> "不及格";
        };
    }

    // toString 重写
    @Override
    public String toString() {
        return String.format("Student{name='%s', age=%d, score=%.1f}", name, age, score);
    }

    // equals 和 hashCode 重写
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Student student = (Student) o;
        return age == student.age
            && Double.compare(student.score, score) == 0
            && java.util.Objects.equals(name, student.name);
    }

    @Override
    public int hashCode() {
        return java.util.Objects.hash(name, age, score);
    }

    public static void main(String[] args) {
        // 创建对象
        Student s1 = new Student("张三", 20, 92.5);
        Student s2 = new Student("李四", 21, 58.0);
        Student s3 = new Student();  // 无参构造

        System.out.println(s1);
        System.out.println(s1.getName() + " 的等级: " + s1.getGrade());
        System.out.println(s2.getName() + " 是否及格: " + s2.isPassed());
        System.out.println("默认学生: " + s3);
    }
}

this 关键字

public class ThisDemo {
    private String name;
    private int age;

    // 1. 区分同名的实例变量和局部变量
    public ThisDemo(String name, int age) {
        this.name = name;   // this.name 是字段,name 是参数
        this.age = age;
    }

    // 2. 调用其他构造器(必须在第一行)
    public ThisDemo() {
        this("未知", 0);   // 调用两参构造器
    }

    // 3. 传递当前对象引用
    public void printSelf() {
        System.out.println("当前对象: " + this);
    }

    // 4. 链式调用(Builder 模式常用)
    public ThisDemo setName(String name) {
        this.name = name;
        return this;
    }

    public ThisDemo setAge(int age) {
        this.age = age;
        return this;
    }

    public String getInfo() {
        return name + ", " + age + "岁";
    }

    public static void main(String[] args) {
        ThisDemo obj = new ThisDemo();
        System.out.println(obj.getInfo());  // 未知, 0岁

        // 链式调用
        String info = new ThisDemo()
            .setName("张三")
            .setAge(25)
            .getInfo();
        System.out.println(info);  // 张三, 25岁
    }
}

封装(Encapsulation)

访问修饰符

修饰符同类同包子类不同包
private
(default)
protected
public
public class BankAccount {
    // 私有字段 —— 外部不能直接访问
    private String owner;
    private double balance;
    private final String accountNo;

    // 构造器
    public BankAccount(String owner, String accountNo, double initialBalance) {
        this.owner = owner;
        this.accountNo = accountNo;
        this.balance = Math.max(0, initialBalance);
    }

    // 公开的 Getter —— 只读
    public String getOwner() { return owner; }
    public String getAccountNo() { return accountNo; }
    public double getBalance() { return balance; }

    // 公开的业务方法 —— 通过方法控制访问
    public boolean deposit(double amount) {
        if (amount <= 0) {
            System.out.println("存款金额必须大于 0");
            return false;
        }
        balance += amount;
        System.out.printf("存款 %.2f 元,余额: %.2f 元%n", amount, balance);
        return true;
    }

    public boolean withdraw(double amount) {
        if (amount <= 0) {
            System.out.println("取款金额必须大于 0");
            return false;
        }
        if (amount > balance) {
            System.out.println("余额不足,当前余额: " + balance);
            return false;
        }
        balance -= amount;
        System.out.printf("取款 %.2f 元,余额: %.2f 元%n", amount, balance);
        return true;
    }

    public static void main(String[] args) {
        BankAccount account = new BankAccount("张三", "622200123456", 1000);
        account.deposit(500);
        account.withdraw(200);
        account.withdraw(2000);  // 余额不足
        // account.balance = 999999;  // ❌ 编译错误,private 字段
    }
}

构造器详解

import java.util.Objects;

public class Product {
    private final String id;      // final 字段只能在构造器中赋值
    private String name;
    private double price;
    private int stock;

    // 主构造器
    public Product(String id, String name, double price, int stock) {
        this.id = Objects.requireNonNull(id, "id 不能为 null");
        this.name = name;
        setPrice(price);   // 使用 setter 进行校验
        setStock(stock);
    }

    // 便捷构造器(委托给主构造器)
    public Product(String id, String name, double price) {
        this(id, name, price, 0);
    }

    // 静态工厂方法(替代构造器的另一种方式)
    public static Product createFreeProduct(String id, String name) {
        return new Product(id, name, 0.0, -1);  // -1 表示无限库存
    }

    // Getter / Setter
    public String getId() { return id; }
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public double getPrice() { return price; }

    public void setPrice(double price) {
        if (price < 0) throw new IllegalArgumentException("价格不能为负");
        this.price = price;
    }

    public int getStock() { return stock; }

    public void setStock(int stock) {
        if (stock < -1) throw new IllegalArgumentException("库存不能为 -1 以下");
        this.stock = stock;
    }

    public boolean isAvailable() {
        return stock != 0;
    }

    @Override
    public String toString() {
        return String.format("Product{id='%s', name='%s', price=%.2f, stock=%d}",
                           id, name, price, stock);
    }

    public static void main(String[] args) {
        Product p1 = new Product("P001", "Java 书", 59.9, 100);
        Product p2 = new Product("P002", "键盘", 299.0);
        Product p3 = Product.createFreeProduct("P003", "赠品");

        System.out.println(p1);
        System.out.println(p2);
        System.out.println(p3);
        System.out.println("是否有库存: " + p1.isAvailable());
    }
}

Record(JDK 16+)

Record 是不可变数据类的简写,自动生成构造器、getter、equals、hashCode、toString:

// 传统写法需要 50+ 行,Record 只需一行
public record Point(int x, int y) {
    // 可以添加自定义方法
    public double distanceTo(Point other) {
        return Math.sqrt(Math.pow(this.x - other.x, 2) + Math.pow(this.y - other.y, 2));
    }

    // 紧凑构造器(校验参数)
    public Point {
        if (x < 0 || y < 0) throw new IllegalArgumentException("坐标不能为负");
    }
}

// 使用
public class RecordDemo {
    public static void main(String[] args) {
        Point p1 = new Point(3, 4);
        Point p2 = new Point(0, 0);

        System.out.println(p1);              // Point[x=3, y=4]
        System.out.println(p1.x());          // 3(注意:不是 getX())
        System.out.println(p1.y());          // 4
        System.out.println(p1.distanceTo(p2)); // 5.0

        // Record 支持 equals 和 hashCode
        Point p3 = new Point(3, 4);
        System.out.println(p1.equals(p3));   // true
    }
}

Record vs 传统类对比

维度Record传统类
声明record Point(int x, int y)50+ 行
可变性不可变(final 字段)可自定义
继承不能继承其他类可以继承
实现接口✅ 可以✅ 可以
可以有方法
适用场景数据传输对象(DTO)、值对象复杂业务对象

⚠️ 注意事项

  1. 构造器中不要调用可被子类重写的方法 — 对象未完全初始化,可能产生 NPE。
  2. final 字段必须在构造器结束前赋值 — 否则编译错误。
  3. 不要在 Getter 中返回可变集合的引用 — 应返回副本或不可变视图。
  4. equals 和 hashCode 必须同时重写 — 否则在 HashMap 等容器中行为异常。

💡 技巧

  1. 防御性拷贝

    public class Schedule {
        private final List<String> tasks;
        public Schedule(List<String> tasks) {
            this.tasks = new ArrayList<>(tasks); // 拷贝,防止外部修改
        }
        public List<String> getTasks() {
            return Collections.unmodifiableList(tasks); // 返回不可变视图
        }
    }
    
  2. Objects 工具方法

    Objects.requireNonNull(name, "name 不能为空");
    Objects.hash(name, age);  // 生成 hashCode
    Objects.equals(a, b);     // null 安全的 equals
    
  3. 使用 Lombok 减少样板代码

    @Data @AllArgsConstructor @NoArgsConstructor
    public class User {
        private String name;
        private int age;
    }
    

🏢 业务场景

  • DTO(Data Transfer Object): Record 非常适合在 API 层和数据层之间传输数据。
  • 不可变对象: StringBigDecimalLocalDate 都是不可变的,线程安全。
  • Builder 模式: 当构造器参数过多时使用,如 HTTP 客户端配置。
  • 值对象: MoneyEmailAddressPhoneNumber 等业务概念封装为类。

📖 扩展阅读