- 0
- 551 words
上一篇文章中,我们用 new Student() 创建了一个学生对象,然后通过 setter 方法逐个设置属性:
Student s = new Student();
s.setName("张三");
s.setAge(20);
s.setStudentId("20240001");
s.setScore(85.5);
每次创建对象后都要手动调 setter,是不是有点麻烦?而且有些属性是对象一出生就必须具备的——比如一个人出生时就有名字和出生日期。Java 提供了一个优雅的解决方案:构造器(Constructor)。
一、什么是构造器?
构造器是一个特殊的方法,在创建对象时自动调用。它的主要作用是初始化对象的状态。
1.1 构造器的语法
[访问修饰符] 类名([参数列表]) {
// 初始化代码
}
构造器的三个特点:
- 名称必须与类名完全相同
- 没有返回值类型(连
void都不能写) - 创建对象时自动调用,不能手动调用
1.2 默认构造器
细心的读者可能会问:我们上一篇文章的 Student 类明明没写任何构造器,为什么 new Student() 能编译通过?
答案是:如果一个类没有显式定义任何构造器,Java 会自动提供一个无参的默认构造器,它的实现是空的,什么都不做。
// 默认构造器等价于:
public Student() {
// 空实现
}
重要规则:一旦你手动定义了任何构造器,默认构造器就不再自动提供了。也就是说,如果你只写了一个带参构造器,new Student() 就无法编译,除非你把无参构造器也手动补上。
二、无参构造器与带参构造器
2.1 编写无参构造器
无参构造器通常用来给属性设置默认值:
public class Student {
private String name;
private int age;
private String studentId;
private double score;
// 无参构造器:设置初始值
public Student() {
this.name = "未知";
this.age = 18;
this.studentId = "00000000";
this.score = 60.0;
System.out.println("Student 对象已创建,默认初始化完成!");
}
// ... getter/setter 省略(同上一篇文章)
}
public class TestStudent {
public static void main(String[] args) {
Student s = new Student(); // 自动调用无参构造器
s.showInfo();
}
}
输出:
Student 对象已创建,默认初始化完成!
===== 学生信息 =====
姓名:未知
年龄:18
学号:00000000
成绩:60.0
等级:及格
===================
2.2 带参构造器:一步到位地初始化
带参构造器允许我们在创建对象时就传入初始数据,省去后续调 setter 的麻烦:
public class Student {
// ... 属性声明同上
// 无参构造器
public Student() {
this.name = "未知";
this.age = 18;
this.studentId = "00000000";
this.score = 60.0;
}
// 带参构造器
public Student(String name, int age, String studentId, double score) {
this.name = name;
this.age = age;
this.studentId = studentId;
this.score = score;
}
// ... 其余代码
}
现在创建对象可以一步到位:
public class TestStudent {
public static void main(String[] args) {
// 创建时直接给值
Student s = new Student("张三", 20, "20240001", 85.5);
s.showInfo();
}
}
是不是简洁多了?new Student("张三", 20, "20240001", 85.5) 这一行就完成了之前 5 行代码的工作。
三、构造器重载(Overload)
3.1 什么是方法重载?
重载(Overload)是指在同一个类中,方法名相同但参数列表不同。构造器也可以重载,也就是说我们可以定义多个不同参数列表的构造器,让使用者按需选择。
构造器重载的判断依据:
- 参数个数不同
- 参数类型不同
- 参数顺序不同
注意:仅返回值类型不同不能算重载(构造器本来也没有返回值)。
3.2 实际例子
为 Student 提供多种创建方式:
public class Student {
private String name;
private int age;
private String studentId;
private double score;
// ① 无参构造器
public Student() {
this.name = "未知";
this.age = 18;
this.studentId = "00000000";
this.score = 60.0;
}
// ② 只传姓名和年龄
public Student(String name, int age) {
this.name = name;
this.age = age;
this.studentId = "待分配";
this.score = 0;
}
// ③ 传所有信息
public Student(String name, int age, String studentId, double score) {
this.name = name;
this.age = age;
this.studentId = studentId;
this.score = score;
}
// 注意:下面这个不是重载!和③的参数类型顺序不同但实际是一样的(编译器无法区分)
// public Student(String studentId, double score, String name, int age) // ❌ 不建议
}
使用时的灵活性:
public class TestStudent {
public static void main(String[] args) {
Student s1 = new Student(); // 使用 ①,默认值
Student s2 = new Student("李四", 22); // 使用 ②
Student s3 = new Student("王五", 19, "20240002", 92.0); // 使用 ③
}
}
四、this() 调用:构造器之间的协作
4.1 为什么要在一个构造器中调用另一个构造器?
观察上面的代码,你会发现一个严重的问题:大量重复代码。三个构造器都在给同样的四个属性赋值。如果将来增加了一个属性(比如 gender),就得修改三个构造器——很容易漏改。
Java 提供了 this() 语法:在构造器中调用本类的另一个构造器。
4.2 用法与规则
public class Student {
private String name;
private int age;
private String studentId;
private double score;
// ① 核心构造器:接收所有参数
public Student(String name, int age, String studentId, double score) {
this.name = name;
this.age = age;
this.studentId = studentId;
this.score = score;
}
// ② 只传姓名和年龄
public Student(String name, int age) {
// 使用 this() 调用构造器 ①,其他用默认值
this(name, age, "待分配", 0);
}
// ③ 无参构造器
public Student() {
// 调用构造器 ②,传入默认值
this("未知", 18);
}
}
核心规则:
this()必须写在构造器的第一行- 一个构造器只能调用一次
this()(因为必须放在第一行,且只能有一个第一行) - 不能形成循环调用(A 调 B,B 调 A——编译报错)
4.3 完整的 Student 类
把前面学到的封装知识结合起来,实现一个完整的 Student 类:
public class Student {
private String name;
private int age;
private String studentId;
private double score;
// 带校验的核心构造器
public Student(String name, int age, String studentId, double score) {
setName(name); // 调用 setter 复用校验逻辑
setAge(age);
setStudentId(studentId);
setScore(score);
}
// 委托到核心构造器
public Student(String name, int age) {
this(name, age, "待分配", 0);
}
public Student() {
this("未知", 18);
}
// getter/setter 带校验
public String getName() { return name; }
public void setName(String name) {
if (name == null || name.trim().isEmpty()) {
throw new IllegalArgumentException("姓名不能为空!");
}
this.name = name;
}
public int getAge() { return age; }
public void setAge(int age) {
if (age 60) {
throw new IllegalArgumentException("年龄必须在6~60之间!");
}
this.age = age;
}
public String getStudentId() { return studentId; }
public void setStudentId(String studentId) {
if (studentId == null || !studentId.matches("\d{8}")) {
throw new IllegalArgumentException("学号必须为8位数字!");
}
this.studentId = studentId;
}
public double getScore() { return score; }
public void setScore(double score) {
if (score 100) {
throw new IllegalArgumentException("成绩必须在0~100之间!");
}
this.score = score;
}
public void showInfo() {
System.out.println("===== 学生信息 =====");
System.out.println("姓名:" + name);
System.out.println("年龄:" + age);
System.out.println("学号:" + studentId);
System.out.println("成绩:" + score);
System.out.println("等级:" + getGrade());
System.out.println("===================");
}
private String getGrade() {
if (score >= 90) return "优";
if (score >= 80) return "良";
if (score >= 70) return "中";
if (score >= 60) return "及格";
return "不及格";
}
}
五、综合案例:银行账户类
下面设计一个简单的银行账户类,综合运用封装、构造器和构造器重载:
public class BankAccount {
private String accountNo; // 账号
private String ownerName; // 户主姓名
private double balance; // 余额
private double annualRate; // 年利率(百分比,如3.5)
// 核心构造器
public BankAccount(String accountNo, String ownerName,
double balance, double annualRate) {
this.accountNo = accountNo;
this.ownerName = ownerName;
this.balance = balance;
this.annualRate = annualRate;
}
// 开户(默认余额为0,利率按基准)
public BankAccount(String accountNo, String ownerName) {
this(accountNo, ownerName, 0, 2.5);
}
// 默认构造器(预留)
public BankAccount() {
this("未开户", "未知", 0, 0);
}
// getter / setter
public String getAccountNo() { return accountNo; }
public String getOwnerName() { return ownerName; }
public double getBalance() { return balance; }
public void setAnnualRate(double annualRate) {
if (annualRate 10) {
System.out.println("年利率必须在0~10之间!");
return;
}
this.annualRate = annualRate;
}
public double getAnnualRate() { return annualRate; }
// 存款
public void deposit(double amount) {
if (amount <= 0) {
System.out.println("存款金额必须大于0!");
return;
}
balance += amount;
System.out.println("存款成功,当前余额:" + balance + " 元");
}
// 取款
public void withdraw(double amount) {
if (amount balance) {
System.out.println("余额不足!当前余额:" + balance + " 元");
return;
}
balance -= amount;
System.out.println("取款成功,当前余额:" + balance + " 元");
}
// 计算年利息
public double calcYearlyInterest() {
return balance * annualRate / 100;
}
public void showInfo() {
System.out.println("===== 银行账户 =====");
System.out.println("账号:" + accountNo);
System.out.println("户主:" + ownerName);
System.out.println("余额:" + balance + " 元");
System.out.println("年利率:" + annualRate + "%");
System.out.println("预计年利息:" + calcYearlyInterest() + " 元");
System.out.println("===================");
}
}
测试代码:
public class TestBank {
public static void main(String[] args) {
// 开户
BankAccount acc = new BankAccount("6222021234567890", "张三", 10000, 3.5);
acc.showInfo();
System.out.println("n--- 存款操作 ---");
acc.deposit(5000);
System.out.println("n--- 取款操作 ---");
acc.withdraw(2000);
acc.withdraw(20000); // 余额不足
System.out.println("n--- 最终信息 ---");
acc.showInfo();
}
}
输出:
===== 银行账户 =====
账号:6222021234567890
户主:张三
余额:10000.0 元
年利率:3.5%
预计年利息:350.0 元
===================
--- 存款操作 ---
存款成功,当前余额:15000.0 元
--- 取款操作 ---
取款成功,当前余额:13000.0 元
余额不足!当前余额:13000.0 元
--- 最终信息 ---
===== 银行账户 =====
账号:6222021234567890
户主:张三
余额:13000.0 元
年利率:3.5%
预计年利息:455.0 元
===================
结语
本章我们深入学习了构造器的方方面面:
- 构造器的定义和使用
- 默认构造器的自动生成规则
- 无参构造器与带参构造器
- 构造器重载提供多种初始化方式
this()调用避免重复代码- 综合案例演示完整实践
掌握了类、封装、构造器之后,你已经能设计出健壮的 Java 类了。下一篇文章我们将继续探讨 Java 面向对象的其他基础设施:包机制、访问权限、static 关键字,并通过一个综合案例把这一阶段的知识融会贯通。
敬请期待!