# 2026-07-21 构造方法(构造器)基础练习(课堂练习用) > **说明**:以下习题围绕今日授课核心内容——**构造方法(构造器)** 展开,涵盖构造方法的概念、格式、重载、`private` 构造方法以及构造方法与封装的综合应用。 > > 题目分为两部分:**随堂练习**(构造方法入门,6 题)和 **进阶练习**(综合挑战,10 题)。 --- # 第一部分:随堂练习 > 以下练习围绕构造方法的基础概念和用法,每题独立可练。 --- ## 练习 1:无参构造方法——Cat 类 **难度**:⭐ **知识点**:类定义、无参构造方法 创建一个 `Cat` 类,包含: - **私有属性**:`name`(String)、`age`(int) - **无参构造方法**:输出一条消息 `"一只小猫出生了!"`,并将 `name` 赋值为 `"小咪"`,`age` 赋值为 `1` - **getter/setter** 方法 **测试类要求**:创建 `Cat` 对象,通过 getter 输出 name 和 age 的值。 **预期输出**: ``` 一只小猫出生了! name = 小咪, age = 1 ``` --- ## 练习 2:有参构造方法——Dog 类 **难度**:⭐ **知识点**:有参构造方法 创建一个 `Dog` 类,包含: - **私有属性**:`name`(String)、`breed`(品种,String) - **有参构造方法**:`Dog(String name, String breed)` —— 通过参数为属性赋值 - **方法**:`showInfo()` —— 输出 `"品种:xxx,名字:xxx"` **测试类要求**:使用有参构造方法创建 2 个 `Dog` 对象,分别调用 `showInfo()` 输出信息。 **示例输出**: ``` 品种:金毛,名字:旺财 品种:哈士奇,名字:二哈 ``` --- ## 练习 3:构造方法重载——Book 类 **难度**:⭐⭐ **知识点**:构造方法重载 创建一个 `Book` 类,包含: - **私有属性**:`title`(书名,String)、`author`(作者,String)、`price`(价格,double) - **3 个重载的构造方法**: - 无参构造:书名 = `"未知"`,作者 = `"未知"`,价格 = `0.0` - 双参构造:`Book(String title, String author)` —— 书名和作者由参数传入,价格默认为 `0.0` - 全参构造:`Book(String title, String author, double price)` —— 所有属性由参数传入 - **方法**:`showInfo()` 输出所有属性信息 **测试类要求**:分别使用 3 种构造方法创建 3 个 Book 对象,调用 `showInfo()` 输出。 **示例输出**: ``` 书名:未知,作者:未知,价格:0.0 书名:Java从入门到精通,作者:张三,价格:0.0 书名:设计模式,作者:李四,价格:59.0 ``` --- ## 练习 4:this 关键字在构造方法中应用——Phone 类 **难度**:⭐⭐ **知识点**:构造方法 + this 关键字 创建一个 `Phone` 类,包含: - **私有属性**:`brand`(品牌,String)、`price`(价格,double)、`color`(颜色,String) - **构造方法**:全参构造 `Phone(String brand, double price, String color)` —— 使用 `this` 关键字区分同名参数 - **方法**:`call(String name)` —— 输出 `"品牌xxx的手机正在给yyy打电话"` **测试类要求**:使用全参构造创建对象,调用 `call()` 方法。 **示例输出**: ``` 品牌华为的手机正在给张三打电话 ``` --- ## 练习 5:private 构造方法——Singleton 体验 **难度**:⭐⭐⭐ **知识点**:private 构造方法 创建一个 `Manager` 类,包含: - **私有属性**:`name`(String) - **私有构造方法**:`private Manager()` —— 构造方法内部输出 `"Manager 对象已创建"` - **公有** `static` 方法:`public static Manager getInstance()` —— 内部 `new Manager()` 并返回该对象 - **getter/setter** **测试类要求**: 1. 尝试直接 `new Manager()`,观察编译错误 2. 通过 `Manager.getInstance()` 方法获取 Manager 对象,设置 name 并输出 **提示**:这是单例设计模式的雏形——通过私有构造方法 + 静态方法控制对象的创建。 --- ## 练习 6:默认构造方法 vs 显式定义 **难度**:⭐⭐ **知识点**:默认构造方法被覆盖 创建一个 `StudentV3` 类,包含: - **私有属性**:`name`(String)、`score`(int) - **定义一个 2 参构造方法**:`StudentV3(String name, int score)` - **注意**:不要定义无参构造方法 **测试类要求**: 1. 尝试 `new StudentV3()` —— 观察编译是否报错(因为显式定义了构造方法,默认无参构造被覆盖) 2. 使用 `new StudentV3("小明", 90)` 创建对象,通过 getter 输出信息 **思考**:为什么 `new StudentV3()` 会报错? --- # 第二部分:进阶练习 > 以下为综合挑战题,融合构造方法与封装、对象数组、Random、方法等知识点,难度逐步递增。 --- ## 第 1 题:构造方法 + 封装——Computer 类 ⭐ 创建一个 `Computer` 类,包含: - **私有属性**:`brand`(品牌)、`cpu`(CPU 型号)、`memory`(内存大小,int,单位 GB)、`price`(价格) - **无参构造方法**:品牌 = `"未知品牌"`,CPU = `"未知CPU"`,内存 = `8`,价格 = `3999.0` - **全参构造方法**:所有属性通过参数传入 - **getter/setter** - **方法**:`showConfig()` 输出电脑配置信息 **测试类要求**:分别使用无参构造和全参构造创建两个 Computer 对象,调用 `showConfig()` 输出。 --- ## 第 2 题:构造方法 + 对象数组——创建多个对象 ⭐⭐ 定义一个 `Hero`(英雄)类,包含: - **私有属性**:`name`(英雄名)、`hp`(血量,int)、`attack`(攻击力,int) - **全参构造方法**:`Hero(String name, int hp, int attack)` - **getter/setter** - **方法**:`showInfo()` 输出英雄信息 **测试类要求**: 1. 创建 5 个 `Hero` 对象(数据自拟:如"盖伦" 580 100、"剑圣" 450 130 等) 2. 存入 `Hero[]` 数组 3. 遍历数组,调用每个英雄的 `showInfo()` 方法 --- ## 第 3 题:构造方法 + setter 校验——BankAccount 类 ⭐⭐ 创建一个 `BankAccount` 类,包含: - **私有属性**:`accountNo`(账号,String)、`owner`(户主,String)、`balance`(余额,double) - **全参构造方法**:`BankAccount(String accountNo, String owner, double balance)` - 在构造方法中加入校验:如果 `balance` 小于 0,则输出 `"初始余额不能为负数,已设置为0.0"`,并将余额强制设为 `0.0` - **getter/setter**(setBalance 也要有同样的校验) - **方法**:`deposit(double amount)` 存款(金额必须大于0),`withdraw(double amount)` 取款(不能透支) **测试类要求**: 1. 使用全参构造创建一个余额为负数的账户,观察构造方法中的校验输出 2. 测试存款、取款功能 --- ## 第 4 题:构造方法重载 + 默认值——Employee 类 ⭐⭐ 创建一个 `Employee` 类,包含: - **私有属性**:`id`(工号)、`name`(姓名)、`position`(职位)、`salary`(薪资) - **3 个重载的构造方法**: - 全参构造 - `Employee(String id, String name, String position)` —— 薪资默认为 `5000.0` - `Employee(String id, String name)` —— 职位默认 `"普通员工"`,薪资默认 `3500.0` - **方法**:`work()` 输出 `"工号xxx的员工xxx正在工作,职位:xxx,薪资:xxx"` **测试类要求**:使用 3 种构造方法分别创建对象,调用 `work()` 方法。 --- ## 第 5 题:构造方法 + 方法调用——Circle ⭐⭐ 创建一个 `Circle` 类,包含: - **私有属性**:`radius`(半径,double) - **构造方法**: - 无参构造:半径 = `1.0` - 有参构造:`Circle(double radius)` - **setter** 中校验半径必须大于 0,否则提示 `"半径必须大于0"` - **方法**: - `getArea()` 返回面积(π × r²) - `getPerimeter()` 返回周长(2 × π × r) **测试类要求**:创建两个 Circle 对象(一个无参,一个有参),分别输出面积和周长。 > **提示**:π 取 `3.1415926`,面积和周长的返回值为 `double` 类型。 --- ## 第 6 题:构造方法 + this() 调用——Student 类 ⭐⭐⭐ > **注意**:`this(参数)` 在一个构造方法中调用另一个构造方法,必须写在构造方法的第一行。 创建一个 `StudentV4` 类,包含: - **私有属性**:`id`(学号,int)、`name`(姓名,String)、`className`(班级,String) - **构造方法**(使用 `this()` 互相调用): - 无参构造:调用全参构造,传入 `0, "未知", "未分配"` - `StudentV4(int id, String name)`:调用全参构造,传入 `id, name, "默认班"` - `StudentV4(int id, String name, String className)`:直接为属性赋值 - **方法**:`showInfo()` 输出所有信息 **测试类要求**:分别使用 3 种构造方法创建对象,输出验证。 **示例输出**: ``` 学号:0,姓名:未知,班级:未分配 学号:1001,姓名:张三,班级:默认班 学号:1002,姓名:李四,班级:J260713 ``` --- ## 第 7 题:构造方法 + 对象数组 + 遍历打印——Movie 类 ⭐⭐⭐ 创建一个 `Movie`(电影)类,包含: - **私有属性**:`title`(片名)、`director`(导演)、`year`(上映年份,int)、`rating`(评分,double) - **全参构造方法** - **getter/setter** - **方法**:`showInfo()` 输出电影信息 **测试类要求**: 1. 创建一个 `Movie[]` 数组,存储至少 4 部电影 2. 使用 **全参构造方法一次性初始化** 所有电影对象(不要用无参构造 + setter) 3. 遍历数组,输出评分最高的电影信息 **示例数据**: ``` "肖申克的救赎", "弗兰克·德拉邦特", 1994, 9.7 "霸王别姬", "陈凯歌", 1993, 9.6 "阿甘正传", "罗伯特·泽米吉斯", 1994, 9.5 "泰坦尼克号", "詹姆斯·卡梅隆", 1997, 9.4 ``` --- ## 第 8 题:构造方法 + Random + 方法——猜数游戏(面向对象版)⭐⭐⭐⭐ 将猜数字游戏改造为面向对象版本,使用构造方法初始化游戏: **`GuessGame` 类**: - **私有属性**:`target`(目标数字,int)、`maxRange`(最大范围,int)、`attempts`(已猜次数,int) - **构造方法**:`GuessGame(int maxRange)` —— 在 `1 ~ maxRange` 之间用 Random 生成目标数字 - **方法**: - `guess(int num)` —— 判断猜测结果,返回 `String`:`"猜对了!"` / `"大了,再小点"` / `"小了,再大点"` - `getAttempts()` —— 返回已猜次数 **`GuessGameTest` 测试类**: 1. 创建 `GuessGame` 对象,范围 1~100 2. 使用 `Scanner` 让用户循环输入猜测数字 3. 调用 `guess()` 方法判断,直到猜对为止 4. 猜对后输出总共猜了多少次 --- ## 第 9 题:构造方法 + 封装 + 对象数组 + 排序——BookManager ⭐⭐⭐⭐ 创建一个 `BookV2` 类: - **私有属性**:`id`(编号,int)、`title`(书名)、`price`(价格,double) - **全参构造方法** - **getter/setter** 创建一个 `BookManager` 类,包含以下方法: - `initBooks()` —— 创建并返回一个包含 5 本图书的 `BookV2[]` 数组(使用全参构造初始化) - `printBooks(BookV2[] books)` —— 遍历输出所有图书信息 - `sortByPrice(BookV2[] books)` —— 按价格从高到低排序(冒泡排序) - `searchByTitle(BookV2[] books, String title)` —— 根据书名查找图书,返回找到的图书对象(找不到返回 `null`) **`BookManagerTest` 测试类**: 1. 调用 `initBooks()` 初始化图书数组 2. 调用 `printBooks()` 输出原始顺序 3. 调用 `sortByPrice()` 排序后再次输出 4. 调用 `searchByTitle()` 查找某本书,输出查找结果 --- ## 第 10 题:综合大挑战——学生成绩管理系统(构造方法版)⭐⭐⭐⭐⭐ 使用构造方法完成完整的学生成绩管理系统,在包 `system` 下创建以下类: **`StudentV5` 类**(实体类): - **私有属性**:`id`(学号,int)、`name`(姓名,String)、`javaScore`(Java 成绩,double)、`mysqlScore`(MySQL 成绩,double) - **全参构造方法** - **getter/setter** - **方法**:`getAverage()` 返回两门课的平均分 **`StudentSystem` 类**(业务类): - **私有属性**:`StudentV5[] students`(学生数组)、`count`(当前学生数量) - **构造方法**:`StudentSystem(int capacity)` —— 初始化指定容量的学生数组 - **方法**: - `addStudent(StudentV5 s)` —— 将学生添加到数组中(如果数组已满,提示"数组已满,添加失败") - `printAll()` —— 输出所有学生信息(含平均分) - `sortByAverage()` —— 按平均分降序排序(冒泡排序) - `getTopStudent()` —— 返回平均分最高的学生对象 **`SystemTest` 类**(测试类): 1. 创建 `StudentSystem` 对象,容量为 5 2. 使用全参构造创建 5 个学生对象 3. 调用 `addStudent()` 逐个添加学生 4. 调用 `printAll()` 输出所有学生信息 5. 调用 `sortByAverage()` 排序后再次输出 6. 调用 `getTopStudent()` 输出最高分学生信息 --- ## 第 11 题:综合大挑战——MyArray 可变数组(动态数组底层实现)⭐⭐⭐⭐⭐ > 模仿 `ArrayList` 的底层原理,自己动手实现一个可变数组(动态数组)。本题综合运用 **构造方法、数组扩容、方法封装、排序算法** 等核心知识点。 ### 需求背景 Java 中普通数组 `int[]` 一旦创建,长度就固定了,无法动态增减元素。请自行实现一个 **可变整型数组** `MyArray` 类,内部使用 `int[]` 存储数据,对外提供便捷的增删改查方法。 --- ### 实体类说明:MyArray **私有属性**: | 属性 | 类型 | 说明 | |------|------|------| | `data` | `int[]` | 内部真正存储数据的数组 | | `size` | `int` | 当前已存储的元素个数(不是数组长度) | **构造方法**: | 构造方法 | 说明 | |---------|------| | `MyArray()` | 无参构造,默认初始化容量为 **10** 的数组 | | `MyArray(int capacity)` | 指定初始容量,如果传入的容量 ≤ 0,则使用默认容量 10 | > **提示**:构造方法中只需 `data = new int[capacity]; size = 0;`,不要预填数据。 --- ### 核心方法列表 要求实现以下 **11 个方法**: #### 1. 添加元素 — `add(int element)` 将元素添加到数组的末尾。 - 添加前先检查内部数组是否已满(`size == data.length`) - 如果已满,**自动扩容**为当前容量的 **1.5 倍**(即 `data.length * 3 / 2`) - 扩容步骤:创建新数组 → 将旧数组元素复制到新数组 → 将新数组赋值给 `data` - 最后将新元素存入 `data[size]`,`size` 自增 1 #### 2. 获取元素 — `get(int index)` 返回指定下标处的元素值。 - 如果下标越界(`index < 0 || index >= size`),输出 `"下标越界,无法获取元素"`,返回 `-1` - 否则返回 `data[index]` #### 3. 修改元素 — `set(int index, int element)` 将指定下标处的元素修改为新值。 - 如果下标越界,输出 `"下标越界,无法修改元素"`,不执行修改 - 否则将 `data[index]` 赋值为 `element` #### 4. 获取长度 — `size()` 返回当前已存储的元素个数(`size` 属性值)。 #### 5. 获取容量 — `capacity()` 返回内部数组的总长度(`data.length`)。 #### 6. 判断是否为空 — `isEmpty()` 如果 `size == 0` 返回 `true`,否则返回 `false`。 #### 7. 查找元素下标 — `indexOf(int element)` 从前向后遍历数组,查找第一个值与 `element` 相等的元素。 - 如果找到,返回该元素的下标 - 如果没找到,返回 `-1` #### 8. 判断是否包含 — `contains(int element)` 调用 `indexOf()` 方法,根据返回值是否为 `-1` 来判断。 #### 9. 删除指定位置的元素 — `remove(int index)` 删除指定下标处的元素。 - 如果下标越界,输出 `"下标越界,无法删除元素"`,返回 `-1` - 否则: - 用临时变量保存被删除的元素值 - 将 `index` 之后的所有元素向前移动一位(`data[j] = data[j+1]`) - `size` 自减 1 - 返回被删除的元素值 > **示意图**(删除下标 2 的元素): > > ```text > 删除前:[10, 20, 30, 40, 50] size=5 > 删除后:[10, 20, 40, 50, 0] size=4 > ``` #### 10. 删除指定值的元素 — `removeByValue(int element)` 删除数组中第一个值与 `element` 相等的元素。 - 调用 `indexOf(element)` 获取下标 - 如果下标为 `-1`,输出 `"未找到该元素,删除失败"`,返回 `false` - 否则调用 `remove(index)` 方法,返回 `true` #### 11. 数组排序 — `sort(boolean ascending)` 对数组中的元素进行排序。 - 参数 `ascending` 为 `true` 时**升序排列**(小→大),为 `false` 时**降序排列**(大→小) - 使用**冒泡排序**或**选择排序**实现 - 注意:只对已存储的元素(`data[0] ~ data[size-1]`)排序,不要动后面的空闲位置 --- ### 辅助方法 #### `print()` 遍历输出数组中的所有元素,格式为 `[元素1, 元素2, 元素3, ...]`。 - 如果数组为空,输出 `[]` - 最后一个元素后面不要有逗号 #### `clear()` 清空数组中的所有元素,将 `size` 置为 `0`(内部数组对象保留,不清除)。 --- ### 参考代码骨架 ```java public class MyArray { private int[] data; // 内部存储数组 private int size; // 当前元素个数 // 无参构造:默认容量 10 public MyArray() { // TODO } // 有参构造:指定初始容量 public MyArray(int capacity) { // TODO(capacity <= 0 时使用默认容量 10) } // 添加元素(自动扩容) public void add(int element) { // TODO } // 获取指定位置的元素 public int get(int index) { // TODO(下标越界检查) return 0; // placeholder } // 修改指定位置的元素 public void set(int index, int element) { // TODO } // 获取元素个数 public int size() { return size; } // 获取数组容量 public int capacity() { return data.length; } // 判断是否为空 public boolean isEmpty() { // TODO return false; } // 查找元素下标 public int indexOf(int element) { // TODO return -1; } // 判断是否包含某元素 public boolean contains(int element) { // TODO return false; } // 删除指定位置的元素,返回被删除的值 public int remove(int index) { // TODO(下标越界检查) return 0; // placeholder } // 删除指定值的元素(第一个匹配项) public boolean removeByValue(int element) { // TODO return false; } // 排序(true 升序,false 降序) public void sort(boolean ascending) { // TODO(冒泡排序) } // 遍历输出 public void print() { // TODO } // 清空数组 public void clear() { // TODO } } ``` --- ### 测试类要求:MyArrayTest 在 `main` 方法中按顺序完成以下操作,并观察输出是否符合预期: ```java public class MyArrayTest { public static void main(String[] args) { // 1. 创建一个初始容量为 3 的 MyArray 对象 MyArray arr = new MyArray(3); // 2. 输出初始状态 System.out.println("初始容量:" + arr.capacity()); // 3 System.out.println("是否为空:" + arr.isEmpty()); // true // 3. 添加 5 个元素(触发自动扩容) for (int i = 1; i <= 5; i++) { arr.add(i * 10); // 添加 10, 20, 30, 40, 50 } arr.print(); // [10, 20, 30, 40, 50] // 4. 测试各个方法 System.out.println("元素个数:" + arr.size()); // 5 System.out.println("当前容量:" + arr.capacity()); // 6(3→4→6,扩容了两次) System.out.println("下标2的元素:" + arr.get(2)); // 30 System.out.println("是否包含40:" + arr.contains(40)); // true System.out.println("50的下标:" + arr.indexOf(50)); // 4 // 5. 测试修改 arr.set(1, 99); arr.print(); // [10, 99, 30, 40, 50] // 6. 测试删除 int deleted = arr.remove(2); System.out.println("被删除的元素:" + deleted); // 30 arr.print(); // [10, 99, 40, 50] // 7. 测试按值删除 arr.removeByValue(10); arr.print(); // [99, 40, 50] // 8. 测试升序排序 arr.sort(true); arr.print(); // [40, 50, 99] // 9. 测试降序排序 arr.sort(false); arr.print(); // [99, 50, 40] // 10. 测试越界访问 System.out.println(arr.get(10)); // 下标越界,无法获取元素 → -1 arr.remove(10); // 下标越界,无法删除元素 → -1 // 11. 测试清空 arr.clear(); System.out.println("清空后是否为空:" + arr.isEmpty()); // true arr.print(); // [] } } ``` --- ### 挑战加分项(可选) 如果完成了基础版本,可以进一步挑战以下扩展功能: 1. **泛型支持**:将 `MyArray` 改造为支持任意类型的 `MyArrayList`(使用泛型,存储 `Object[]`) 2. **缩容机制**:当元素个数少于容量的 1/4 时,自动缩容为当前容量的一半(节省内存) 3. **`getMax()` / `getMin()`**:返回数组中的最大值和最小值 4. **`addFirst(int element)`**:在数组头部插入元素(所有元素后移) 5. **`toArray()`**:返回一个包含所有元素的新数组(长度 = size) 6. **`subList(int fromIndex, int toIndex)`**:返回从 `fromIndex` 到 `toIndex` 的子数组视图 --- > **本题设计思路**:这个练习是模拟 JDK 中 `ArrayList` 的底层实现。通过手写动态数组,你能深刻理解: > - 构造方法如何初始化内部数据结构 > - 数组长度固定的局限性以及「扩容」机制的实现原理 > - 删除元素时「元素前移」的内存操作细节 > - 方法封装如何对外提供简洁的 API,隐藏内部实现复杂度 > - 这是通往「数据结构与算法」的第一级台阶 🪜 --- ## 📋 题目总览 | 序号 | 所属部分 | 核心知识点 | 难度 | |------|---------|-----------|------| | 练习 1 | 随堂 | 无参构造方法、对象创建 | ⭐ | | 练习 2 | 随堂 | 有参构造方法、属性初始化 | ⭐ | | 练习 3 | 随堂 | 构造方法重载(3 种形式) | ⭐⭐ | | 练习 4 | 随堂 | 构造方法 + this 关键字 | ⭐⭐ | | 练习 5 | 随堂 | private 构造方法、单例模式雏形 | ⭐⭐⭐ | | 练习 6 | 随堂 | 默认构造方法被覆盖 | ⭐⭐ | | 进阶 1 | 进阶 | 无参/全参构造方法、封装 | ⭐ | | 进阶 2 | 进阶 | 全参构造 + 对象数组 + 遍历 | ⭐⭐ | | 进阶 3 | 进阶 | 构造方法中参数校验、封装 | ⭐⭐ | | 进阶 4 | 进阶 | 构造方法重载 + 默认值 | ⭐⭐ | | 进阶 5 | 进阶 | 构造方法 + 方法封装 + 校验 | ⭐⭐ | | 进阶 6 | 进阶 | this() 构造方法间调用 | ⭐⭐⭐ | | 进阶 7 | 进阶 | 全参构造 + 对象数组 + 遍历查找 | ⭐⭐⭐ | | 进阶 8 | 进阶 | 构造方法 + Random + Scanner + 循环 | ⭐⭐⭐⭐ | | 进阶 9 | 进阶 | 构造方法 + 封装 + 对象数组 + 排序 + 查找 | ⭐⭐⭐⭐ | | 进阶 10 | 进阶 | 综合大挑战(构造方法 + 封装 + 包 + 对象数组 + 排序 + 平均分) | ⭐⭐⭐⭐⭐ | | 进阶 11 | 进阶 | MyArray 可变数组(构造方法 + 数组扩容 + 增删改查 + 元素前移 + 冒泡排序 + 方法封装) | ⭐⭐⭐⭐⭐ |