×

上一篇文章中,我们用 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);
    }
}

核心规则:

  1. this() 必须写在构造器的第一行
  2. 一个构造器只能调用一次 this()(因为必须放在第一行,且只能有一个第一行)
  3. 不能形成循环调用(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 关键字,并通过一个综合案例把这一阶段的知识融会贯通。

敬请期待!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

作者

2248768396@qq.com

相关文章