Java 完全指南 / 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)、值对象 | 复杂业务对象 |
⚠️ 注意事项
- 构造器中不要调用可被子类重写的方法 — 对象未完全初始化,可能产生 NPE。
- final 字段必须在构造器结束前赋值 — 否则编译错误。
- 不要在 Getter 中返回可变集合的引用 — 应返回副本或不可变视图。
- equals 和 hashCode 必须同时重写 — 否则在 HashMap 等容器中行为异常。
💡 技巧
防御性拷贝:
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); // 返回不可变视图 } }Objects 工具方法:
Objects.requireNonNull(name, "name 不能为空"); Objects.hash(name, age); // 生成 hashCode Objects.equals(a, b); // null 安全的 equals使用 Lombok 减少样板代码:
@Data @AllArgsConstructor @NoArgsConstructor public class User { private String name; private int age; }
🏢 业务场景
- DTO(Data Transfer Object): Record 非常适合在 API 层和数据层之间传输数据。
- 不可变对象:
String、BigDecimal、LocalDate都是不可变的,线程安全。 - Builder 模式: 当构造器参数过多时使用,如 HTTP 客户端配置。
- 值对象:
Money、EmailAddress、PhoneNumber等业务概念封装为类。