本文档从 课后作业(
课后作业\)和 课堂练习题(课堂练习题\)中,按 JavaSE 授课章节精选出每个章节的 重点编程题 与 核心编程题,用于期末复习与刷题。筛选原则:
- 🎯 核心编程题:每个章节必须掌握的经典题目,直接对应章节核心知识点
- ⭐ 重点编程题:综合性强、笔试面试高频、值得反复练习的进阶题目
每个题目保留了原文档的核心要求、示例输出与实现提示(详见各章节原文)。
| 章节 | 对应日期 | 核心知识点 |
|---|---|---|
| 第 1 章 运算符与流程控制 | 07-14 | if-else / 逻辑与位运算 / 三目 / 循环 |
| 第 2 章 数组基础 | 07-15 | 数组创建 / 遍历 / 增删改查 / 去重 |
| 第 3 章 数组进阶与排序 | 07-16 | 冒泡 / 选择 / 插入排序、Scanner |
| 第 4 章 Java 方法 | 07-17 | 方法 4 种声明 / 数组参数 / Random |
| 第 5 章 面向对象基础与封装 | 07-20 | 类与对象 / private / setter / getter |
| 第 6 章 构造方法 | 07-21 | 无参/有参/重载 / this() / 对象数组 |
| 第 7 章 继承 | 07-22 | extends / super / 方法重写 / 多态雏形 |
| 第 8 章 抽象类与接口 | 07-23 | abstract / interface / 模板方法 |
| 第 9 章 多态 | 07-24 | 向上/向下转型 / instanceof / 动态数组 |
| 第 10 章 匿名内部类与 JavaAPI | 07-27 | 匿名内部类 / String / StringBuilder / Math / 时间日期 |
| 第 11 章 异常处理 | 07-28 | try-catch / throw / throws / 自定义异常 / finally |
| 第 12 章 集合框架(ArrayList) | 07-29 | ArrayList / 泛型 / 增删改查 / 底层扩容 |
| 第 13 章 LinkedList 与双链表 | 07-30 | Queue / Stack / Deque / 手写链表 |
| 第 14 章 Map 与迭代器 | 07-31 | Iterator / HashMap / TreeMap / keySet / entrySet |
| 第 15 章 比较器排序与可变参数 | 08-03 | Comparable / Comparator / Collections / 可变参数 / Lambda |
| 第 16 章 Stream 流与函数式编程 | 08-04 | filter / map / collect / 方法引用 / 期末大作业 |
| 第 17 章 File 与 IO 流 | 08-06 | File 类 / FileInputStream / FileOutputStream |
| 第 18 章 IO 缓冲流与字符流 | 08-07 | 缓冲流 / FileReader / FileWriter / BufferedReader |
| 第 19 章 对象序列化与多线程 | 08-08 | Serializable / 转换流 / Thread / Runnable / Callable |
| 第 20 章 多线程进阶 | 08-10 | 生命周期 / 优先级 / 守护线程 / synchronized |
| 第 21 章 volatile 与 Lock 锁 | 08-11 | volatile / ReentrantLock / 线程池 |
| 第 22 章 反射 | 08-12~13 | Class / Constructor / Field / Method / JUnit 模拟 |
出处:
课后作业/2026年7月14日-作业.md
知识点:if-else、逻辑运算符、算术运算符
从键盘输入一个年份(整数),判断该年份是否为闰年。
闰年规则(满足其一即可):
要求:定义 int year 变量;使用 if-else 分支结构;使用逻辑运算符 && 和 ||;输出 xxxx 年是闰年 或 xxxx 年不是闰年。
// 核心判断:能被4整除但不能被100整除,或能被400整除
boolean isLeap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
知识点:三目运算符、if-else
定义三个整数变量 a、b、c,找出其中的最大值并输出。
方法二:使用 if-else 实现(两种方法都写在同一个文件中)
int max = (a > b ? a : b) > c ? (a > b ? a : b) : c;
知识点:算术运算符(/、%)、if-else
"水仙花数"指一个三位数,其各位数字的立方和等于该数本身。例如:153 = 1³ + 5³ + 3³。
int num = 153;
int bai = num / 100; // 百位
int shi = num / 10 % 10; // 十位
int ge = num % 10; // 个位
// 判断 bai³ + shi³ + ge³ == num
知识点:if-else if-else、逻辑运算符、三目运算符
| 分数范围 | 等级 | 评价语 |
|---|---|---|
| 90 ≤ score ≤ 100 | A | 优秀,继续保持! |
| 80 ≤ score < 90 | B | 良好,再接再厉! |
| 70 ≤ score < 80 | C | 中等,还需努力! |
| 60 ≤ score < 70 | D | 及格,危险边缘! |
| 0 ≤ score < 60 | E | 不及格,加油啊! |
| 其他 | 无效 | 分数错误! |
额外使用三目运算符判断该成绩是否及格(≥ 60 为及格)。
知识点:if-else、算术运算符、Scanner(选做)
定义两个整数 a、b 和字符变量 operator(取值为 '+'/'-'/'*'/'/'/'%'),用 if-else if-else 多分支判断输出运算结果。除号为 /、取模为 % 时需判断除数是否为 0,输出"除数不能为 0"。
知识点:算术运算符、位运算符、三目运算符、综合运用
设计一个 4 位数字加密系统:
1234)加密结果 ^ 0xAA 再异或)知识点:位运算符(&、|、~、<<)、综合运用
用位运算实现权限控制:
int READ = 1 << 0; // 0001 = 1 读权限
int WRITE = 1 << 1; // 0010 = 2 写权限
int EXECUTE = 1 << 2; // 0100 = 4 执行权限
int DELETE = 1 << 3; // 1000 = 8 删除权限
| 赋予权限:userPerm = READ | WRITE | EXECUTE; → 7& 判断权限:boolean hasWrite = (userPerm & WRITE) == WRITE;& ~ 取消权限:userPerm = userPerm & ~WRITE; → 5知识点:运算符优先级、自增/自减、短路特性
先手算再运行验证:
int a = 5, b = 3, c = 2;
int result1 = a++ + ++b * c--; // 手算 + 验证
int x = 10, y = 4;
boolean flag = (x > y) && (++x > 10) || (y-- < 3); // 分析 && 短路
int m = 8, n = 3;
int r = m++ - --n + ++m - n--;
出处:
课后作业/2026年7月15日-作业.md、课堂练习题/20260715-数组进阶练习.md
知识点:动态初始化 new int[5]、静态初始化 {10,20,30,40,50}、索引访问(注意索引从 0 开始)
知识点:for 循环、数组遍历、arr.length 属性
循环条件中必须使用 arr.length 获取数组长度,不能在代码中直接写死数字。
知识点:数组遍历、累加求和
定义整数数组 {11, 22, 33, 44, 55},遍历计算总和。
知识点:数组遍历、比较运算
定义一个变量 max 初始为第一个元素,遍历数组遇到更大的就更新 max。
知识点:索引逆向操作
从 arr.length - 1 开始倒序遍历到 0,不改变原数组内容。
知识点:数组遍历、if-else 分支、取模运算
n % 2 == 0 表示偶数,n % 2 == 1 表示奇数。
知识点:数组遍历、嵌套循环、新数组
定义数组 {5, 2, 8, 2, 5, 9, 1, 8, 3},去除重复元素只保留第一次出现的,不能用 Set 集合。
知识点:数组、随机数、嵌套循环、去重
红球从 1~33 随机选 6 个不重复号码存入 int[] 数组;蓝球从 1~16 随机选 1 个。
int num = (int)(Math.random() * 33) + 1; // 生成候选号码
// 与已生成的号码逐个比较,不重复才加入(去重)
知识点:数组操作、循环、下标运算
{1,2,3,4,5} 循环右移 2 位 → {4,5,1,2,3}。在原数组上操作,k = k % 数组长度 处理 k 大于长度的情况。
出处:
课后作业/2026年7月16日-作业.md
知识点:冒泡排序、嵌套循环、变量交换
对 {29, 10, 14, 37, 13, 33, 48, 22} 升序排列,用 Arrays.toString() 输出前后对比。
arr.length - 1 轮[0, arr.length-1-i] 区间相邻比较,前一个比后一个大就交换优化:用 boolean 标记本轮是否有交换,无交换提前结束
for (int i = 0; i < arr.length - 1; i++) {
boolean swapped = false;
for (int j = 0; j < arr.length - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j]; arr[j] = arr[j + 1]; arr[j + 1] = temp;
swapped = true;
}
}
if (!swapped) break; // 提前结束优化
}
知识点:选择排序、嵌套循环
对 {56, 23, 89, 12, 45, 67, 34, 78} 降序排列。每轮从剩余元素中选出最大值放到未排序部分最前面,用 for 循环遍历输出(不用 Arrays.toString())。
知识点:插入排序、数组遍历
对成绩数组 {88, 72, 93, 65, 81, 97, 78, 85} 升序排列,每轮输出中间结果,排序完成后输出最低分和最高分。
while 从后往前扫描,比当前元素大的就后移知识点:Scanner、数据类型、字符串拼接、条件判断
采集姓名/学号/年龄/性别/身高/体重/是否在校,计算 BMI(weight / (height * height)),按中国标准判定体型(偏瘦/正常/偏胖/肥胖)。
nextInt() / nextDouble() 与 nextLine() 混用时的换行符陷阱String.format("%.2f", bmi) 或 Math.round(bmi * 100) / 100.0出处:
课后作业/2026年7月17日-作业.md、课堂练习题/20260717-Java方法基础练习.md方法 4 种声明方式:
- 无参无返回值(
public static void 方法名() {})- 有参无返回值(
public static void 方法名(参数) {})- 有参有返回值(
public static 类型 方法名(参数) {})- 无参有返回值(
public static 类型 方法名() {})
知识点:4 种方法声明
printInfoCard() 打印个人信息卡片printInvertedTriangle(int n) 打印 n 行倒直角三角形(第 i 行打印 n - i 个 *)average(int[] arr) 计算数组平均值(注意整数除法丢失小数,用 (double)求和 / arr.length)getGreeting() 根据 LocalTime.now().getHour() 返回问候语知识点:方法中调用另一个方法
public static double celsiusToFahrenheit(double c) { return c * 9.0 / 5.0 + 32.0; }
public static double fahrenheitToCelsius(double f) { return (f - 32.0) * 5.0 / 9.0; }
public static void printTemperature(double c) {
// 调用上面两个方法,printf("%.2f") 保留两位小数输出 ℃ / ℉
}
知识点:数组作为方法参数、冒泡排序
将冒泡排序封装为 bubbleSort(int[] arr) 方法,方法内部输出排序前后数组,main 中测试两组数据。
知识点:Random、无参有返回值、字符串拼接
生成 8 位随机密码,每一位随机是大写字母(ASCII 65~90)或数字(48~57)。
Random ran = new Random();
String password = "";
// 循环8次:ran.nextInt(2) 决定当前位是字母还是数字
// 字母:(char)(ran.nextInt(26) + 65);数字:(char)(ran.nextInt(10) + 48)
知识点:数组作为方法参数、Random、数组交换
// 从最后一个元素往前遍历(i 从 length-1 到 1):
// 生成随机索引 j,范围 [0, i];交换 arr[i] 和 arr[j]
for (int i = arr.length - 1; i > 0; i--) {
int j = ran.nextInt(i + 1);
int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp;
}
知识点:方法 4 种声明方式综合、数组参数、Random、方法中调用方法
拆分为多个方法:initParticipants()(返回 10 人姓名数组)、printParticipants(String[] names)(遍历打印)、shuffleArray(String[] arr)(洗牌)、luckyDraw(String[] names)(打乱后取第 1 个元素为中奖者)。
课堂 20 道方法练习题(
课堂练习题/20260717-Java方法基础练习.md):打印分隔线 / 九九乘法表 / 判断奇偶 / 打印质数(Math.sqrt(n)优化)/ 三角形 / 等腰三角形 / 求和 / 三数最大值 / 圆面积 / 闰年 / 阶乘 / 验证码 / 骰子 / 随机颜色 RGB 等,均围绕 4 种方法声明。
出处:
课后作业/2026年7月20日-作业.md、课堂练习题/20260720-面向对象基础练习.md
知识点:类与对象、成员变量、成员方法
定义 Student 类(name / age / className),方法 showInfo() 输出信息、study(String course) 输出"张三 正在学习 Java"。main 中创建 2 个对象。注意同一个 .java 文件只能有一个 public class。
知识点:封装(private、setter/getter)、this 关键字、合法性校验
私有属性 id/name/salary,标准 setter/getter(参数同名用 this 区分)。setSalary 中薪资小于 0 输出"薪资不能为负数!"。业务方法 work() 输出"工号 xxx 的员工 xxx 正在工作……"。
知识点:数组作为方法参数、三大排序算法、Arrays.copyOf
将冒泡排序(升序+提前结束优化)、选择排序(降序)、插入排序(升序)封装为三个方法。用 Arrays.copyOf(arr, arr.length) 创建副本,避免改变原数组。
知识点:方法 4 种声明方式综合、质数算法、回文数算法、Random
| 方法 | 类型 | 说明 |
|---|---|---|
printPrime() |
无参无返回值 | 输出 1~100 质数,每行 5 个(检查到 Math.sqrt(n)) |
printSum(int n, int m) |
有参无返回值 | 输出 n~m 累加和 |
isPalindrome(int num) |
有参有返回值 | 判断回文数(12321 / 12345 / 1221) |
getLuckyNumber() |
无参有返回值 | Random 生成 1~100 幸运数字 |
知识点:封装、对象数组、平均分、排序、查找
在包 com.lovecoding.system 下:Student 类(id/name/scores[3] + getAverage());StudentSystem 类(initData()、printAll()、sortByAverage() 降序、searchByName() 返回对象/null);SystemTest 测试类。
出处:
课后作业/2026年7月21日-作业.md、课堂练习题/20260721-构造方法练习.md
知识点:私有属性、全参构造、对象数组、遍历找最值
Movie 类:title/director/year/rating,全参构造 + getter/setter + showInfo()。
测试:创建 Movie[] 数组存至少 4 部电影,使用全参构造一次性初始化(不用无参+setter),遍历输出评分最高的电影。
知识点:构造方法初始化、Random、Scanner、循环
GuessGame 类:私有 target/maxRange/attempts;构造方法 GuessGame(int maxRange) 用 nextInt(maxRange) + 1 生成目标数字;guess(int num) 返回 "猜对了!"/"大了,再小点"/"小了,再大点" 并自增 attempts;测试类用 while(true) + Scanner 循环猜,猜对 break 输出总次数。
知识点:全参构造、对象数组、冒泡排序、按名查找
BookV2(id/title/price 全参构造);BookManager:
initBooks() — 无参有返回值,创建并返回 5 本图书数组printBooks(BookV2[] books) — 遍历输出sortByPrice(BookV2[] books) — 按价格从高到低冒泡排序searchByTitle(BookV2[] books, String title) — 用 equals() 查找,找不到返回 null知识点:this() 构造方法间调用(必须写在第一行)
无参构造调用全参构造传 0, "未知", "未分配";StudentV4(int id, String name) 调用全参构造传默认班。
知识点:构造方法、数组扩容、增删改查、元素前移、冒泡排序
模仿 ArrayList 底层实现可变整型数组 MyArray(详见第 9 章重点题,内容相同)。
知识点:构造方法 + 封装 + 对象数组 + 排序
StudentV5(id/name/javaScore/mysqlScore + getAverage());StudentSystem(私有 StudentV5[] students + count,构造方法指定容量,addStudent() 满则提示、printAll()、sortByAverage()、getTopStudent() 返回最高分对象)。
出处:
课后作业/2026年7月22日-作业.md、课堂练习题/20260722-继承练习.md
知识点:继承的格式、子类调用父类方法
父类 Appliance(powerOn()/powerOff()),子类 TV(changeChannel())、WashingMachine(startWash())。观察子类直接使用父类方法,不需重复定义。
知识点:super() 调用父类构造、方法重写(Override)
父类 Employee(id/name/salary 全参构造 + work() 输出"员工在工作");子类 Teacher(新增 subject,super(id,name,salary),重写 work() 输出"工号为xxx的xxx老师正在教授xxx课程")、Doctor(新增 specialty,重写 work())。
子类构造方法第一行必须
super(参数);重写的方法签名必须与父类完全一致;通过getId()/getName()获取父类私有属性。
知识点:多层继承、super()、方法重写
Person(name/age + showInfo)→ Student(+school,重写 showInfo)→ Postgraduate(+mentor,重写 showInfo),逐层 super(...) 调用父类构造。
知识点:继承、构造方法 + super()、方法重写、多态、对象数组、冒泡排序
Employee 父类(id/name/baseSalary + getSalary() 返回基本工资 + showInfo());子类 Manager(+bonus)、Programmer(+projectBonus)、Designer(+commission),各自重写 getSalary()。
测试:创建 Employee[] 数组混合存放不同类型员工,调用 showInfo() 观察多态,按 getSalary() 降序冒泡排序,查找薪资最高的员工。
知识点:父类统一方法名、子类各自实现(抽象思想,不强制用 abstract)
Shape(color + getArea() 默认返回 0.0 + showInfo());Circle/Rectangle/Triangle 重写 getArea()。创建 Shape[] 数组存储不同图形,按面积降序排序。
出处:
课后作业/2026年7月23日-作业.md、课堂练习题/20260723-抽象类与接口练习.md
知识点:抽象类、接口、类同时继承抽象类并实现接口、方法重写、多态
Connectable 接口:connect() / print(String content) / close()Printer 抽象类:私有 brand/model + 有参构造 + showInfo() + 抽象方法 maintenance()LaserPrinter(继承 Printer + 实现 Connectable,新增 tonerLevel)、InkjetPrinter(新增 inkLevel),重写全部抽象方法
// 类同时继承抽象类并实现接口:extends Printer implements Connectable
public class LaserPrinter extends Printer implements Connectable { ... }
思考:如果把
Connectable改为抽象类让子类直接继承,会出现 Java 单继承限制的问题。
知识点:接口抽象方法、default 默认方法、static 静态方法、模拟模板方法
Notifiable 接口:
doSend(recipient, message)validateParams()(空值校验)、log()、send()(模板方法:校验 → doSend → log → 结束)printNotificationType()(必须通过 接口名.方法名() 调用)EmailNotifier(重写 doSend + 可选重写 log)、SMSNotifier(重写 doSend,不重写默认方法)。
知识点:抽象类、抽象方法、模板方法(final)、方法重写
ExamTemplate 抽象类:普通方法 login()/deliverPaper()/announceResult(),抽象方法 conductExam()/gradeExam(),模板方法 final void startExam() 按顺序执行完整流程。JavaExam/MathExam 子类只实现两个抽象方法。
startExam()用final修饰防止子类修改流程骨架。
知识点:抽象类、抽象方法重写、多态数组、统计
Payment 抽象类(amount/orderId + 抽象 pay()/refund() + 普通 showPaymentInfo());WeChatPay/AliPay/CreditCard 子类各自重写。创建 Payment[] 数组遍历执行支付/退款,累加 amount 统计总金额。
出处:
课后作业/2026年7月24日-作业.md、课堂练习题/20260724-多态练习.md
知识点:接口多态(参数/数组)、向上转型、向下转型、instanceof、调用实现类特有方法、开闭原则
SmartDevice 接口(turnOn()/doWork()/turnOff());实现类 Light(特有 changeColor())、AirConditioner(特有 setTimer())、RobotVacuum(特有 startCleaningMode())。
// 1. 创建 SmartDevice[] 数组装多种设备
SmartDevice[] devices = { new Light("客厅"), new AirConditioner("主卧", 26), ... };
// 2. 遍历执行通用流程 turnOn → doWork → turnOff
// 3. 遍历用 instanceof 判断后向下转型调用特有方法
if (device instanceof Light) {
((Light) device).changeColor("暖黄");
} else if (device instanceof AirConditioner) {
((AirConditioner) device).setTimer(4);
}
// 4. 封装静态方法 startDevice(SmartDevice device, int seconds)
思考:新增
SmartSpeaker实现类时控制器无需修改 —— 符合开闭原则;instanceof判断的是运行时类型。
知识点:构造方法、数组扩容(1.5 倍)、增删改查、元素前移、冒泡排序
模仿 ArrayList 底层实现(核心 11 个方法):
add(int element) — 满则扩容为 data.length * 3 / 2,扩容三步(建新数组→复制→替换引用)get/set/size/capacity/isEmpty/indexOf/containsremove(int index) — 后面元素前移一位、size--、返回被删值removeByValue(int element) — 调 indexOf + removesort(boolean ascending) — 冒泡排序只对 data[0]~data[size-1]知识点:抽象类、模板方法(final)、方法重写
(同第 8 章重点题 3,父类定义骨架、子类实现步骤。)
知识点:多态数组、instanceof 向下转型、调用特有方法、薪资统计
Employee 抽象类(id/name + 抽象 calculateSalary() + 普通 showInfo());FullTimeEmployee(baseSalary+bonus×1.2)、PartTimeEmployee(时薪×工时)、Intern(固定津贴),各有特有方法。遍历数组按 instanceof 转型调用特有方法,计算总薪资、找最高薪。
知识点:接口多态、Usb 数组、instanceof、开闭原则
Usb 接口(startUsb()/stopUsb());Mouse/Keyboard/USBHub 实现类各有特有方法;NoteBook 类持 Usb[] 数组 + plugIn() + workAll()(遍历执行 start → instanceof 转型调特有方法 → stop)。
出处:
课后作业/2026年7月27日-作业.md、课堂练习题/20260727-内部类练习.md
知识点:匿名内部类三种使用方式(变量赋值 / 方法参数传递 / 匿名对象直接调用)
Payment 接口(pay()/refund());OrderService.processPayment(Payment payment, double amount) 业务类。
Payment wechat = new Payment() { ... }; 实现微信支付service.processPayment(new Payment() { ... }, 299.0); 支付宝showBonus(),通过 new Payment(){...}.showBonus() 直接调用Object 的匿名内部类子类重写 toString()知识点:split / substring / contains / replace / trim / equalsIgnoreCase / StringBuilder.append
data 按逗号分割 + trim() 去空格 + 统计每个姓名出现次数contains() / indexOf() / startsWith() / endsWith()substring() 提取前三个姓名 + replace() 替换names split 出每个姓名,用 append() 逐条拼接生成 HTML <ul><li>...</li></ul> 列表String.join(" | ", 数组) 拼接知识点:Math(PI/pow/sqrt/ceil/floor/round/random/abs/max/min)、LocalDate / LocalTime / LocalDateTime / DateTimeFormatter
Math.random() 生成 6 位验证码、abs() 求温差LocalDate.now()/of()、until() 计算天数差、日期加减 100 天、LocalTime.now()/of()、DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分ss秒") 格式化知识点:匿名内部类作为方法参数(策略模式雏形)、自定义 Comparator/Filter 接口、冒泡排序
StudentManager:sortStudents(Student[], Comparator) / filterStudents(Student[], Filter) / findMax(Student[], Comparator),全部用匿名内部类传参。6 个任务:按成绩降序、按年龄升序、按姓名长度排序、筛选成绩≥85、筛选年龄>20、查找成绩最高。
体会"行为参数化"思想 —— 排序/筛选逻辑作为参数传入方法。
知识点:私有内部类(外界不可见,通过 Bank 公共方法操作)、局部内部类(方法内定义)、静态内部类(RateInfo 共享利率)
Bank 类:私有成员内部类 Account(含局部内部类 DepositTransaction/WithdrawTransaction)、静态内部类 RateInfo(静态常量 SAVING_RATE/LOAN_RATE + calculateInterest() 复利计算)。
出处:
课堂练习题/20260728-异常处理练习.md
知识点:try-catch-finally 综合运用、多异常场景
ScoreManager.sum(Double[] scores):null 数组返回 0、空数组返回 0、null 元素跳过继续累加。average() 中调用 sum() 计算有效成绩个数,用 try-catch 包裹整个方法捕获任意异常。
知识点:throw、业务逻辑校验
四个任务:setAge() 年龄 0~130 否则抛 IllegalArgumentException;withdraw() 金额≤0 或超余额抛异常;parseInt() 空串/非数字字符手动抛 NumberFormatException;验证 throw 后代码不执行(不可达语句)。
知识点:自定义异常(继承 Exception)、throws 声明、多 catch、finally 操作日志
定义 UsernameEmptyException / UsernameLengthException / PasswordEmptyException / PasswordWeakException / AgeInvalidException。RegisterService.register() 用 try-catch-finally 依次验证用户名→密码→年龄,每个验证独立 catch 输出错误提示,finally 输出操作日志。
知识点:自定义业务异常、异常向上抛出、多 catch、finally 交易记录
Account.withdraw() 把异常抛给调用者(throws InvalidAmountException, WithdrawLimitExceededException, InsufficientBalanceException);ATM.withdraw() 用多 catch 分别处理,finally 输出【交易记录】。
思考:
findAccount()返回 null vs 抛AccountNotFoundException的优缺点;finally 保证交易记录一定输出。
知识点:编译期异常(继承 Exception)vs 运行时异常(继承 RuntimeException)
setPrice() 抛 InvalidPriceException(RuntimeException,无需 throws);findProduct() 抛 ProductNotFoundException(Exception,必须 throws)。outbound() 用 try-finally(不处理异常、只保证资源关闭);inbound() 用 try-catch-finally(内部处理 + 关闭)。
知识点:try-finally、try-catch-finally、finally 与 return 执行顺序、finally 修改返回值、finally 中的异常传播
6 个任务验证:
出处:
课后作业/2026年7月29日-作业.md、课堂练习题/20260729-集合框架练习.md
知识点:ArrayList 创建、add()、size()、for+get 遍历、包装类
Student 类(name / Integer age(包装类) / studentId),创建 ArrayList<Student> 存储 4 个学生并遍历输出。
知识点:isEmpty() / set() / remove(index) / add(index, element) / 遍历查找
判断是否为空 → 遍历查找学号 S003 → set() 改名 → remove() 删除 → add(1, 新学生) 指定索引插入 → 再次遍历输出。
知识点:遍历、条件筛选、包装类自动拆箱、冒泡排序
计算平均年龄(Integer 自动拆箱)、统计 ≥20 岁学生、找年龄最大、按年龄冒泡排序。
知识点:ArrayList 底层原理、扩容机制、fail-fast(modCount)、System.arraycopy
逐行注释分析源码并回答思考题:
EMPTY_ELEMENTDATA vs DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的区别(懒加载优化)grow() 中 ArraysSupport.newLength(oldCapacity, minGrowth, oldCapacity >> 1)(1.5 倍扩容)、trimToSize()、ensureCapacity()fastRemove() 中 System.arraycopy 前移、删除后末位置 null、clear() 遍历置 null vs 直接换空数组MyArrayList<E>(无参默认容量 10、自动扩容、CRUD、toString)知识点:ArrayList<ArrayList<Student>> 嵌套模拟二维结构
Classroom 类:seats 为 4 排 × 5 列(初始全 null),assignSeat(row, col, student)(越界检查 + 占用检查)、displaySeats()、countEmptySeats()、countOccupiedInRow(row)。
出处:
课后作业/2026年7月30日-作业.md、课堂练习题/20260730-LinkedList双链表练习.md
知识点:offer() / poll() / peek()、FIFO 特性
用 Queue<String> queue = new LinkedList<>() 模拟叫号:客户 A/B/C 依次 offer 入队 → peek 看队首 → poll 叫号出队 → 新客户 D 入队 → 再次 poll → 输出剩余。空队列 poll()/peek() 返回 null 不抛异常。
Queue<String> queue = new LinkedList<>();
queue.offer("客户A"); queue.offer("客户B"); queue.offer("客户C");
System.out.println("请" + queue.poll() + "前往1号窗口"); // 请客户A前往1号窗口
知识点:push() / pop() / peek()、LIFO 特性、Deque 接口栈语义
用 Deque<String> stack = new LinkedList<>() 模拟浏览器后退:push 访问页面 → peek 看栈顶 → pop 后退 3 次 → 循环 pop 清空。空栈 pop() 抛 NoSuchElementException(与 Queue 的 poll() 不同)。
Java 官方建议用
Deque代替Stack类(Stack 继承 Vector,性能差且不必要的线程安全开销)。
知识点:add / get / set / remove(index) / add(index, element)
知识点:时间复杂度、add(index) 头部插入、get(index) 随机访问、遍历方式
四个实验对比耗时(System.currentTimeMillis()):
知识点:链表数据结构、泛型、Node 节点、增删改查
补全 MyLinked<N>:get(index)(检查越界 + 从头遍历)、remove(index)(找到前驱节点 prevNode,prevNode.next = target.next,删尾节点更新 lastNode)、contains(value)(考虑 null)、toArray()((N[]) new Object[size] 泛型擦除强转)、clear()(断开头节点引用)。
出处:
课后作业/2026年7月31日-作业.md、课堂练习题/20260731-迭代器与Map集合练习.md
知识点:iterator() / hasNext() / next()、for+get vs 迭代器
用 for+get 遍历 ArrayList,再用迭代器统一遍历 ArrayList 和 LinkedList。LinkedList 用 for+get 遍历 O(n²),迭代器是最佳方式。
知识点:put / get / containsKey / remove / keySet / entrySet、键不可重复
创建 HashMap<String, Integer>(学号→成绩),测试 size、get、containsKey、put 覆盖旧值、remove,并用两种方式遍历:① keySet + foreach + get(key);② entrySet + foreach + getKey()/getValue()。创建 TreeMap 观察按键自动排序。
知识点:HashSet 去重(无序)、TreeSet 自动排序(字典序/数值升序)
知识点:HashMap 键唯一性依赖 hashCode 定位 + equals 比较
Student 重写 hashCode()(Objects.hash(id, name))和 equals()。验证:两个属性相同的 Student 对象 equals 为 true、哈希值相同、作为 Key 存入 Map 后 map.size() == 1,用属性相同的新对象 get 也能查到。
原理:不重写时默认 hashCode 基于对象地址 → 两个 new 对象哈希不同 → size=2;先 hashCode 定位,位置有元素再用 equals 比较。
知识点:Map 键值对建模、put / get / containsKey、getOrDefault
Map<String, Integer> map = new HashMap<>();
for (String word : words) {
if (map.containsKey(word)) map.put(word, map.get(word) + 1);
else map.put(word, 1);
}
// 简化:map.put(word, map.getOrDefault(word, 0) + 1);
// 遍历 entrySet 找出现次数最多的单词
知识点:匿名内部类实现 Comparator、Collections.sort()、TreeSet 自定义排序
Student(name/age/score);SortDemo:匿名内部类 Comparator 按年龄升序、按成绩降序(Double.compare),TreeSet 传入按姓名排序的匿名 Comparator。
必做简答题:HashMap 底层原理(数组+链表/红黑树、树化条件 >8 且容量 >64、退化 <6、默认容量 16 负载因子 0.75)。
出处:
课后作业/2026年8月3日-作业.md、课堂练习题/20260803-比较器排序练习.md
知识点:Comparable、compareTo 重写、二次比较、TreeSet 自动排序+去重
Student implements Comparable<Student>,按成绩降序,成绩相同再按姓名:
@Override
public int compareTo(Student o) {
int result = Double.compare(o.getScore(), this.score); // 成绩降序
return result == 0 ? this.name.compareTo(o.getName()) : result; // 二次比较
}
返回值规则:负数存左 / 0 重复不存 / 正数存右。张三和赵六同分但姓名不同 → compareTo 不为 0 → 都能存入。
知识点:Collections.sort(list, comparator)、匿名内部类、Double.compare、降序技巧
Product(name/price/stock)不实现 Comparable。用匿名内部类 Comparator 按价格升序 / 降序(交换参数位置)/ 按库存升序;再用 Collections.max/min(list, comparator) 找最高/最低价。
double 不能用
(int)(p1.getPrice() - p2.getPrice())(浮点精度+强转截断),必须用Double.compare。
知识点:int... a、for each、可变参数本质即数组、可变参数置末
public int add(int... a) { // 本质就是 int[] a
int sum = 0;
for (int n : a) sum += n;
return sum;
}
// 固定参数 + 可变参数(可变参数必须放最后)
public void showScore(String name, int... scores) { ... }
知识点:函数式接口、Lambda 标准格式、三条省略规则
useAdd((a, b) -> a + b); // 单语句省略大括号、分号、return
useFlyable(s -> { ... }); // 单参数省略小括号
Collections.sort(list, (s1, s2) -> Double.compare(s2.getScore(), s1.getScore()));
知识点:Comparable 自然排序 + equals/hashCode 一致性 + TreeSet 去重 + Comparator 按姓名排序
Employee(id/name/salary/dept):compareTo 按薪资降序、薪资相同按工号;equals/hashCode 以工号判定(与 compareTo 口径一致 → 工号相同的自动去重);临时按姓名排序用匿名内部类/Lambda(不改 Employee 类)。
原理:E002 添加两次只存一个,因 compareTo 工号相同返回 0 视为重复;compareTo == 0 → equals 返回 true、hashCode 一致(官方推荐)。
知识点:TreeMap 按键排序、自定义类型作为 Key
把 Student 作为 Key、班级作为 Value 存入 TreeMap<Student, String>,按 compareTo 规则自动排序。
出处:
课堂练习题/20260804-Stream流练习.md、课后作业/2026年8月4日-作业.md(期末综合大作业)
知识点:Collection.stream() / Map 转 Set / Arrays.stream() / Stream.of()
List<String> list = ...; list.stream();
map.keySet().stream(); map.values().stream();
Arrays.stream(array);
Stream.of("长贵", "赵四", "刘能");
知识点:filter(Predicate)、forEach(Consumer)、方法引用
list.stream().filter(s -> s.startsWith("万")).forEach(System.out::println);
// 叠加过滤:filter(s -> s.length() == 3)
// 统计:long count = list.stream().filter(...).filter(...).count();
知识点:limit、skip、分页思想
// 分页通用公式:skip((pageNo-1)*pageSize).limit(pageSize)
Stream.of(0,1,2,3,4,5,6,7,8,9).skip((pageNo-1)*pageSize).limit(pageSize).forEach(...)
知识点:Stream.concat(a, b)(静态方法)、distinct()(依据 equals)
知识点:终结方法只能有一个、count() 返回 long
list.stream().forEach(System.out::println).count(); // 编译报错!终结方法后流已结束
知识点:Collectors.toList() / toSet()
List<String> l = list.stream().distinct().collect(Collectors.toList());
Set<String> s = list.stream().filter(x -> x.contains("伊")).collect(Collectors.toSet());
// toList() 返回不可变集合,add 抛 UnsupportedOperationException
知识点:toMap(keyMapper, valueMapper)、Function 函数式接口、Lambda vs 匿名内部类
Map<Integer, Student> map = list.stream()
.collect(Collectors.toMap(student -> student.getId(), student -> student));
// 匿名内部类等价写法:new Function<Student, Integer>() { apply(...) }
// 键重复默认抛 IllegalStateException
知识点:filter / distinct / count / collect / toMap 综合、方法引用
list.stream().filter(e -> "技术部".equals(e.getDept())).forEach(System.out::println);
list.stream().map(Employee::getName).distinct().forEach(System.out::println); // 按姓名去重
long cnt = list.stream().filter(e -> "技术部".equals(e.getDept())).count();
Map<String, String> map = list.stream().collect(Collectors.toMap(Employee::getId, Employee::getName));
知识点:贯穿整个 JavaSE(封装/继承/抽象类/接口/异常/集合/比较器/多态)+ 模块五 Stream 选做
按模块分层:
Person 抽象类(name/age + 抽象 getRole())、Studyable/Teachable 接口、Student extends Person implements Comparable<Student>, Studyable(学号 id/班级/成绩列表,compareTo 按平均分降序二次比较学号,equals/hashCode 以学号判定)、Teacher、Course、ScoreScoreOutOfBoundsException / StudentNotFoundException / CourseNotFoundException / DuplicateStudentExceptionStudentManager(增删改查,重复学号抛异常,findById 用迭代器)、CourseManager、ScoreManager(HashMap<String, List<Score>> 按学号分组)、StudentSystemApp(Scanner + switch + while 主菜单,try-catch 处理自定义异常)RankUtil 四个排序(TreeSet 自然排序排行榜 / Comparator 按姓名 / Lambda 按年龄 / 按平均分降序)StreamQuery 六个 Stream 查询(filter/map/count/distinct/toMap/sorted)出处:
课后作业/2026年8月6日-作业.md、课堂练习题/20260806-File文件操作练习.md
知识点:FileInputStream / FileOutputStream、read(byte[]) / write(byte[], 0, len)、缓冲区 byte[4096]、-1 判断、close、耗时统计
FileInputStream fis = new FileInputStream(src);
FileOutputStream fos = new FileOutputStream(desc);
byte[] bytes = new byte[4096];
int length;
long start = System.currentTimeMillis();
while ((length = fis.read(bytes)) != -1) { // -1 表示读完
fos.write(bytes, 0, length); // 只写本次有效字节
}
fos.close(); fis.close(); // 先关输出再关输入
long end = System.currentTimeMillis();
System.out.println("复制完成!耗时:" + (end - start) + " 毫秒");
关键:
fis.read(bytes)返回本次有效长度,可能小于缓冲区大小,写入必须用write(bytes, 0, length)截断,否则文件变大。
知识点:new File(String)、mkdir / mkdirs、createNewFile、delete、isFile / isDirectory / exists、getName / getPath / getAbsolutePath / listFiles
mkdir()单级创建(父目录不存在返回 false);mkdirs()多级创建更推荐;createNewFile()编译期异常需处理;delete()删非空目录返回 false;listFiles()返回File[]。
出处:
课后作业/2026年8月7日-作业.md、课堂练习题/20260807-IO缓冲流练习.md
知识点:字符流 = 字节流 + 字符集编码表、FileWriter.write(String) 直接写中文、FileReader.read(char[])、new String(chars, 0, count)
// 写:FileWriter 直接写中文(无需 getBytes)
FileWriter fw = new FileWriter("D:/poem.txt");
fw.write("床前明月光,疑是地上霜。"); fw.write("\r\n");
fw.write("举头望明月,低头思故乡。");
fw.flush(); fw.close();
// 读:read(char[]) 批量读取
FileReader fr = new FileReader("D:/poem.txt");
char[] chars = new char[1024];
int count;
while ((count = fr.read(chars)) != -1) {
System.out.print(new String(chars, 0, count)); // 只取有效部分
}
fr.close();
知识点:BufferedReader / BufferedWriter、readLine() 逐行读取、newLine() 换行、split("-")、Integer.parseInt()、ArrayList + Collections.sort
读 D:/scores.txt("姓名-成绩"顺序乱)→ 五步闭环:读取 → 处理(split+parseInt)→ 集合(封装 Student)→ 排序(按成绩降序)→ 写回(带名次)。
BufferedReader br = new BufferedReader(new FileReader("D:/scores.txt"));
ArrayList<Student> list = new ArrayList<>();
String line;
while ((line = br.readLine()) != null) {
String[] parts = line.split("-");
list.add(new Student(parts[0], Integer.parseInt(parts[1])));
}
Collections.sort(list, (o1, o2) -> o2.getScore() - o1.getScore()); // 成绩降序
BufferedWriter bw = new BufferedWriter(new FileWriter("D:/sorted.txt"));
for (int i = 0; i < list.size(); i++) {
bw.write("第" + (i+1) + "名:" + list.get(i));
bw.newLine(); // 跨平台换行
}
bw.flush(); bw.close(); br.close();
知识点:CopyFile 接口(面向接口编程/多态)、普通字节流 vs 缓冲流、try-catch-finally、耗时对比
CopyFile 接口 String copy(String src, String desc) throws IOException;Copy01(普通字节流 + byte[8192] + try-catch-finally)、BufferedCopy(BufferedOutputStream 包装底层流)。复制同一大文件对比耗时,验证"缓冲流更快"。
关闭顺序:
Copy01先关 fos 再关 fis;BufferedCopy先关缓冲流再关底层流。
知识点:Charset.forName("GBK")、FileWriter(String, Charset) / FileReader(String, Charset)、编码/解码、乱码原理
用 GBK 写入中文 → 默认字符集(UTF-8)读 → 乱码;改用 GBK 读 → 正常。一个汉字 GBK 占 2 字节、UTF-8 占 3 字节。
FileWriter fw = new FileWriter("D:/编码.txt", Charset.forName("GBK"));
FileReader fr = new FileReader("D:/编码.txt", Charset.forName("GBK")); // 指定同一字符集
知识点:Scanner 键盘录入、FileWriter 写入、数据持久化
录入用户名密码写入 D:/user.txt;选做升级:追加模式 new FileWriter(path, true) 多用户注册、FileReader 读取做登录校验。
出处:
课堂练习题/20260808-对象序列化与转换流练习.md、课后作业/2026年8月8日-作业.md
知识点:Serializable 标记接口、ObjectOutputStream.writeObject() / ObjectInputStream.readObject()、集合也是对象
Student implements Serializable + serialVersionUID。把 3 个学生装进 ArrayList,整个集合一次性序列化,再一次性反序列化读回强转遍历。
class Student implements Serializable {
private static final long serialVersionUID = 42L; // 固定版本号
private Integer id; private String name; private Integer age;
// ...
}
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:/students.txt"));
oos.writeObject(list); oos.close();
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:/students.txt"));
ArrayList<Student> readList = (ArrayList<Student>) ois.readObject(); // Object 需强转
知识点:transient 不参与序列化、InvalidClassException、类版本兼容
transient Integer id 序列化后读回为 nullInvalidClassException(JVM 自动版本号变化)serialVersionUID = 42L → 类结构变化也能读(缺失属性赋默认值)知识点:extends Thread、重写 run()、super(name)、start()、run vs start
SumThread extends Thread,super(name) 传线程名,run() 中 1~100 边加边打印 this.getName() + "--->" + i,结束打印求和结果(5050)。main 中创建两个线程 start() 观察输出交错。
直接调
run()不创建新线程,只是普通方法调用 —— 验证 run 与 start 的区别。
知识点:implements Runnable、Thread.currentThread().getName()、Thread(Runnable, name)、实现类对象/匿名内部类/Lambda
// 三种提供任务方式:
new Thread(new CountTask(), "任务线程"); // ① 实现类对象
new Thread(new Runnable(){ public void run(){...} }, "匿名线程"); // ② 匿名内部类
new Thread(() -> { ... }, "Lambda线程"); // ③ Lambda(Runnable 是函数式接口)
// main 主线程也循环打印,观察 4 种线程名交错(主线程与子线程并发,start() 不阻塞)
知识点:Callable<V>(call() 带返回值)、FutureTask<V> 包装、get() 阻塞获取
SumCallable implements Callable<Integer> 求 1~n 的和并 return sum;用 FutureTask<Integer> 包装后 new Thread(futureTask, "线程名").start();ft.get() 阻塞等待取回每个线程结果,main 中汇总。
三种方式对比:run() 无返回值 vs call() 有返回值;实现接口 vs 继承类。
知识点:InputStreamReader / OutputStreamWriter 转码、编码/解码
InputStreamReader isr = new InputStreamReader(new FileInputStream("D:/abc.txt"), "UTF-8");
OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("D:/hello.txt"), "GBK");
char[] chars = new char[1024]; int count;
while ((count = isr.read(chars)) != -1) { osw.write(chars, 0, count); }
osw.close(); isr.close();
转换流是"扩展流(处理流)":不直接操作文件,包装底层字节流增加转码能力;读端指定源字符集、写端指定目标字符集。
出处:
课后作业/2026年8月10日-作业.md、课堂练习题/20260810-线程操作与生命周期练习.md
知识点:setPriority() / getPriority()、Thread.MAX/NORM/MIN_PRIORITY、Thread.State 6 种状态、getState()、join()
用 Lambda 创建 Runnable 运动员任务(3 个线程优先级 10/5/1),每圈 sleep(100) 模拟跑步,用 getState() 观察 NEW → RUNNABLE → TIMED_WAITING → TERMINATED 状态流转,最后 t1.join() 等待。
优先级只是提高获得 CPU 的概率,不保证先执行完(抢占式调度 + 随机性)。
知识点:setDaemon(true) / isDaemon()、JVM 退出机制、守护线程特点
用户线程"主任务"执行 5 秒业务 + 守护线程"心跳监控"每 500ms 报一次心跳。heart.setDaemon(true) 必须在 start() 之前;task.join() 等主任务结束;main 结束后只剩守护线程 → JVM 退出 → 心跳被强制终止。
JVM 退出条件:所有用户线程结束才退出;守护线程被强杀时 finally 不保证执行。
知识点:线程安全问题(数据竞争)、读-改-写复合操作、synchronized 三种写法、线程安全三大特性
3 个窗口线程卖 100 张票。先不加锁复现 Bug(相同票多次出现 + 负数票),再用 synchronized 修复:
public synchronized void sell()(锁 this)synchronized(lock) { ... }if (tickets > 0) 判断与 tickets-- 修改之间是读-改-写复合操作,可能被并发打断;锁对象必须一致(共用同一个 SellTicket 对象)。知识点:Thread.State 枚举、NEW / RUNNABLE / TIMED_WAITING / TERMINATED
在 start() 前(NEW)、start() 后(RUNNABLE)、sleep 期间(TIMED_WAITING)、join 结束后(TERMINATED)分别打印状态。
知识点:join() / join(long)、interrupt() / isInterrupted() 中断标志、catch 后重设标志、yield()
yield() 让出 CPU + sleep(200),主线程 join() 等它结束interrupt() 中断,catch 中 Thread.currentThread().interrupt() 重设中断标志
> interrupt() 不会杀线程,只是设置中断标志请求线程自行响应;InterruptedException 抛出时标志被清除。出处:
课后作业/2026年8月11日-作业.md、课堂练习题/20260811-volatile与Lock锁练习.md
知识点:JVM 内存模型(工作内存 CPU 缓存 vs 主内存)、volatile 三大语义、方法引用创建线程
private volatile boolean running = true;;work() 中 while(running) 空循环,main 线程 sleep 后 stop() 修改 running。
new Thread(vd::work, "worker")知识点:count++ 读-改-写 3 步、volatile 不保证原子性、synchronized 保证原子性、线程数组 + join()
volatile Integer count + synchronized increment(),1000 线程并发:
for (Thread t : threads) t.join() 等待所有线程结束再打印知识点:Lock 接口、lock() / unlock()、unlock 必须放 finally、公平锁/非公平锁、方法引用
四种加锁方法 + 10000 线程验证 count 正确:
// ① 基本用法:lock + try + finally unlock(unlock 必须放 finally,否则死锁)
lock.lock();
try { count++; } finally { lock.unlock(); }
// ② tryLock():拿不到就算了,避免死锁
if (lock.tryLock()) { try { count++; return true; } finally { lock.unlock(); } }
return false;
// ③ tryLock(timeout):最多等 1 秒
if (lock.tryLock(1, TimeUnit.SECONDS)) { ... }
// ④ lockInterruptibly():等待锁中可被 interrupt() 打断,catch 后重设中断标志
lock.lockInterruptibly();
知识点:7 个参数、执行流程(核心线程 → 工作队列 → 非核心线程 → 拒绝策略)、拒绝策略、execute/shutdown
ThreadPoolExecutor pool = new ThreadPoolExecutor(
2, // corePoolSize
4, // maximumPoolSize
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
提交 8 个耗时任务(sleep 2 秒),观察:任务 1、2 → 核心线程;3、4 → 工作队列排队;5、6 → 队列满创建临时线程;7、8 → 线程满+队列满 → AbortPolicy 拒绝。打印 getActiveCount() / getQueue().size(),最后 pool.shutdown()。
知识点:synchronized vs Lock 七维对比、Lock 解决数据竞争
把 homework0810 的卖票问题从 synchronized(this) 改造成 ReentrantLock:
while (tickets > 0) {
lock.lock();
try { sell(); } finally { lock.unlock(); }
}
3 个窗口共用同一个 SellTicket 对象 + join() 等待,验证无相同票/负数票。
出处:
课后作业/2026年8月12日-作业.md、课后作业/2026年8月13日-作业.md、课堂练习题/20260812-反射练习.md、课堂练习题/20260813-反射Field字段练习.md反射核心口诀:
- 获取 Class 对象三种方式:
类名.class(编译期最安全)/对象.getClass()(需实例)/Class.forName("全类名")(最灵活、框架最常用)- "含 Declared 的能拿私有":
getDeclaredXxx(s)本类全部含 private、不含父类;getXxx(s)只拿 public 含父类继承- 暴力反射:
setAccessible(true)临时取消访问权限检查- invoke 四种调用规律:实例方法传对象 / 静态方法传 null / void 返回 null / private 先 setAccessible(true)
知识点:getConstructors() / getDeclaredConstructors()、getConstructor(参数类型...) / getDeclaredConstructor、newInstance()、setAccessible(true)
SysUser 有 4 个构造(public 无参 / private 二参 / public 六参 / private 四参):
// ① public 无参
clazz.getConstructor().newInstance();
// ② public 六参
clazz.getConstructor(String.class, String.class, String.class, String.class, String.class, String.class)
.newInstance(id, username, password, name, phone, email);
// ③ private 二参(必须暴力反射)
Constructor<?> c = clazz.getDeclaredConstructor(String.class, String.class);
c.setAccessible(true); // 放开私有访问权限
c.newInstance("admin", "admin888");
每个对象 instanceof SysUser 判断 + 打印 toString() 验证。
对比实验:注释
setAccessible(true)抛 IllegalAccessException;用不带 Declared 的getConstructor拿 private 抛 NoSuchMethodException。
知识点:getDeclaredField("属性名")、setAccessible(true)、field.set(对象, 值) / field.get(对象)
Class<?> clazz = Class.forName("course.Student");
Object obj = clazz.getConstructor().newInstance(); // 反射创建对象
Field f = clazz.getDeclaredField("name");
f.setAccessible(true);
f.set(obj, "张三");
System.out.println(f.get(obj)); // 读取私有属性值
知识点:getDeclaredFields()、field.getModifiers() + Modifier.toString()、getType().getSimpleName()、getName()
public static void printFields(Class<?> clazz) {
for (Field field : clazz.getDeclaredFields()) {
String modifier = Modifier.toString(field.getModifiers());
String type = field.getType().getSimpleName();
String name = field.getName();
System.out.println(modifier + " " + type + " " + name);
}
}
printFields(Student.class);
printFields(String.class); // JDK 核心类也能反射
知识点:getDeclaredMethod、setAccessible(true)、invoke(对象) / invoke(null)、通用工具
封装不区分权限的通用工具 invokeAnyMethod:
public static Object invokeAnyMethod(Object obj, String methodName, Object... args) throws Exception {
Class<?>[] paramTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) paramTypes[i] = args[i].getClass();
Method m = obj.getClass().getDeclaredMethod(methodName, paramTypes);
m.setAccessible(true); // 暴力反射
return m.invoke(obj, args); // 实例方法传对象;静态方法 obj 传 null
}
// 调用 private 实例方法 read() → invoke(s)
// 调用 private static 方法 write() → invoke(null)
invoke 四种调用规律:①实例方法传对象 ②静态方法传 null ③void 返回 null ④private 先 setAccessible(true)。静态方法不依赖对象,传什么都会被忽略。
知识点:反射三件套(Constructor + Field + Method)、getDeclaredMethods() 扫描、startsWith("test") 过滤、InvocationTargetException → getCause()
给 Student 补充 3 个 private 测试方法(testAdd 正常 / testHello 正常 / testFail 故意抛异常),写迷你测试框架:
Object obj = Student.class.getConstructor().newInstance(); // 反射创建被测对象
int pass = 0, fail = 0;
for (Method m : Student.class.getDeclaredMethods()) {
if (m.getName().startsWith("test")) { // 过滤测试方法
m.setAccessible(true);
try {
m.invoke(obj); // 暴力反射调用
System.out.println("测试通过:" + m.getName()); pass++;
} catch (InvocationTargetException e) {
System.out.println("测试失败:" + m.getName() + ",原因:" + e.getCause().getMessage()); fail++;
}
}
}
System.out.println("测试通过 " + pass + " 个,失败 " + fail + " 个");
方法内部抛异常 → invoke 抛 InvocationTargetException(包装一层),需
getCause()取真实异常;这就是 JUnit 扫描 @Test 注解方法的底层原理。
知识点:getField("字段名")、field.get/set、封装 readField / setField
用反射修改 public 字段 email/phone 后,用普通访问 s.email 验证 —— 反射 set 与普通访问是同一份数据。
知识点:getDeclaredField + setAccessible(true) + set/get
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field f = obj.getClass().getDeclaredField(fieldName);
f.setAccessible(true);
f.set(obj, value);
}
public static Object getFieldValue(Object obj, String fieldName) throws Exception { ... f.get(obj) ... }
知识点:静态字段反射操作(get(null) / set(null, 值))、ORM 底层原理(Field.set 映射数据库行→Java 对象)
① 静态字段:schoolField.get(null) 读取、schoolField.set(null, "哈尔滨佛学院") 修改,Student.school 直接访问验证全类共享(静态成员属于类、不传对象)。
② 模拟 ORM:通用方法 mapToObject(Map<String, Object> row, Class<?> clazz) —— 反射创建空对象 + getDeclaredFields() 遍历 + 字段名匹配 row 的 key + setAccessible(true) + field.set(obj, row.get(fieldName))。换实体类不改代码(MyBatis 结果映射的底层)。
数据库字段
user_namevs Java 属性userName(下划线转驼峰)就是 MyBatismapUnderscoreToCamelCase的底层逻辑。
知识点:getMethods()(public 含父类继承)vs getDeclaredMethods()(本类全部含 private)、getParameterTypes() + Stream 映射
public static void printMethods(Class<?> clazz, boolean declared) {
Method[] methods = declared ? clazz.getDeclaredMethods() : clazz.getMethods();
for (Method m : methods) {
List<String> params = Arrays.stream(m.getParameterTypes())
.map(Class::getSimpleName).collect(Collectors.toList());
System.out.println(m.getName() + "(" + params + ") 返回值=" + m.getReturnType().getSimpleName());
}
}
getMethods()能看到 toString/hashCode 等 Object 继承方法;getDeclaredMethods()能看到 private 的 read/write。
知识点:getMethod("方法名", 参数类型...)、重载必须传参数类型、invoke(对象, 参数...)、void 返回 null
// 封装通用工具:根据 args 动态计算参数类型数组
public static Object invokeMethod(Object obj, String methodName, Object... args) throws Exception {
Class<?>[] paramTypes = new Class[args.length];
for (int i = 0; i < args.length; i++) paramTypes[i] = args[i].getClass();
Method m = obj.getClass().getMethod(methodName, paramTypes); // 重载必须传参数类型
return m.invoke(obj, args);
}
// void 方法 invoke 返回 null;有返回值返回 Object 可强转
知识点:Properties 加载配置文件、Class.forName 动态加载、反射 Constructor + Field + Method 全家桶、配置与代码分离
student.properties 配置类名/属性/方法名,代码只写"读配置 + 反射"通用逻辑:
Properties props = new Properties();
props.load(ClassLoader.getSystemClassLoader().getResourceAsStream("student.properties"));
Class<?> clazz = Class.forName(props.getProperty("student.className")); // 动态加载类
Object obj = clazz.getConstructor().newInstance();
// 反射给属性赋值 + getMethod(配置的方法名).invoke(obj) 调用
换类不改代码 —— 把配置文件改成另一个类一行代码不改即可运行,这就是"配置驱动 + 反射解耦",也是 Spring IOC 创建 Bean 的过程。
本文档由课后作业与课堂练习题整理而成,题目详细要求、思考题与参考代码请回看对应日期原文。