java题目集4-6总结
一、前言
概括:经过了三次java的大作业的练习,也算是深入java了。这三次大作业的难度是层层递进的,虽然一次比一次难,但是每一次大作业都是基于前面大作业的知识点。所以每一次大作业认真完成,并认真总结知识点,多花点时间,大作业还是勉强可以完成。
1.知识点:
大作业4:大作业四还是答题判题程序的迭代,但增加了继承的使用,程序中SingleChoiceQuestion、MultipleChoiceQuestion、FillInTheBlanksQuestion都是Question的子类,继承了父类的属性和方法。相比之前的答题判题程序,使用了多种数据结构来组织数据,如使用Map来存储题库、试卷信息等。且增加了异常处理使用Optional类来避免空指针异常,例如在getScoreForQuestion方法中,代码相比之前更加健壮了。这也是此次答题判题程序中改进的地方。
大作业5:大作业五,使用到了接口,重写方法,List 接口,Map 接口。大部分都是有关Java集合框架,通过这些接口存储和操作集合数据。
大作业6:大作业六进一步考察了抽象类和接口的使用,设计展示了一个智能家居系统的基本框架,通过定义抽象类和具体设备类,实现了设备的多态性和可扩展性。PinandDevice类负责处理设备连接和控制命令,通过Map数据结构管理设备和连接关系。进一步体现了面向对象的思维。
2.题量与难度
这三次大作业题量都差不多,由于第四次大作业是答题判题程序的最后一次迭代,难度下降了。第五,六次大作业难度是一次比一次大。不过每一次大作业都是基于以前大作业的知识点,进一步增加新的知识点。使用了更多的类及其方法,要从网上查找资料,了解相关类的使用。这些大作业无非都以对象和类的形式存在,使用到了面向对象的三个特性:封装、继承、多态。所有通过写这些大作业,java设计与编程能力可以得到很大提升,每一次多要认真对待。
二、设计与分析
1.第四次大作业
概括:由于是答题判题程序的最后一次迭代,难度相较于之前有所下降。
第一题
校园角色类设计:这次设计要用到继承,角色Role分两类:学生Student和雇员Employee;雇员又分为教员Faculty和职员Staff。设计子类Faculty和Staff继承雇员Employee,通过使用关键字super调用父类构造方法来创建子类实例
第二题
设计一个学生类和它的一个子类——本科生类:也是与第一题类似,学生类作为父类,本科生类作为子类继承学生类。在测试类Main的main( )方法中,调用Student类的带参数的构造方法创建对象object1,调Undergraduate类的带参数的构造方法创建对象object2,然后分别调用它们的show( )方法。
第三题
此题是答题判题程序的最后一次迭代,也是终于来到了答题判题程序的终点(属于是摆脱这个噩梦了/(ㄒoㄒ)/~~)。本次迭代新增了多选题和填空题的判断,之前都是单选题。此次要考虑考虑多个同学有多张不同试卷的答卷的情况,之前只是考虑一个学生不同答卷情况。最重要的一点是,此次要用到继承,继承这个概念在前几作大作业都没有使用到,这次新增了这个要求。
类图

顺序图

下面是代码的分析以及一些解释和心得:
QuestionType枚举
使用枚举,用于表示不同类型的题目,如单选、多选和填空。这是一个简单但非常有用的设计,因为它允许在代码中以一种清晰且类型安全的方式引用这些类型。
//枚举,表示题目种类
enum QuestionType {
SINGLE_CHOICE,
MULTIPLE_CHOICE,
FILL_IN_THE_BLANKS
}
Question 类
- 属性:
id:问题的唯一标识符。
content:问题的内容或描述。
answer:问题的标准答案。
type:问题的类型,使用 QuestionType 枚举表示(尽管枚举的定义未在代码中给出,但可以推断其存在)。 - 构造方法:接收问题的 id、content、answer 和 type 作为参数,并初始化这些属性。
方法:
getId()、getContent()、getAnswer() 和 getType():分别用于获取问题的 id、content、answer 和 type。
isPartiallyCorrect(String givenAnswer):一个私有方法,用于判断填空题的部分正确性。它通过比较标准答案和给定答案(去除空格并转换为小写)的包含关系来实现。
checkAnswer(String givenAnswer):一个公开方法,根据问题的类型(单选题、多选题或填空题)来判断给定答案的正确性。它返回一个 Serializable 对象(这里实际返回的是 Boolean 或 String),表示答案是否完全正确、部分正确或完全错误。 - 子类
SingleChoiceQuestion:单选题类,继承自 Question 类。其构造方法直接调用父类的构造方法,没有添加额外的功能或属性。
MultipleChoiceQuestion:多选题类,同样继承自 Question 类。构造方法也是直接调用父类的构造方法。
FillInTheBlanksQuestion:填空题类,继承自 Question 类。构造方法直接调用父类的构造方法。
public Serializable checkAnswer(String givenAnswer) 是 Question 类中的一个重要方法,用于检查用户提供的答案是否正确。这个方法根据不同的题型(单选题、多选题、填空题)有不同的检查逻辑。
点击查看代码
private boolean isPartiallyCorrect(String givenAnswer) {
// 删除标准答案中的空格,以便于比较
String trimmedAnswer = answer.replace(" ", "").toLowerCase();
String trimmedGivenAnswer = givenAnswer.replace(" ", "").toLowerCase();
// 检查部分正确性
return trimmedGivenAnswer.contains(trimmedAnswer)
|| trimmedAnswer.contains(trimmedGivenAnswer)
&& !trimmedGivenAnswer.isEmpty()
&& !trimmedAnswer.isEmpty();
}
public Serializable checkAnswer(String givenAnswer) {
switch (type) {
case SINGLE_CHOICE:
return answer.equals(givenAnswer.trim());
case MULTIPLE_CHOICE:
var stdAns = new HashSet<>(Arrays.asList(answer.split(" ")));
var ans = new HashSet<>(Arrays.asList(givenAnswer.trim().split(" ")));
// 完全正确:答案包含所有正确答案
if (stdAns.containsAll(ans) && ans.size() == stdAns.size()) {
return true;
}
// 部分正确:答案包含部分正确答案且不含错误答案
if (stdAns.containsAll(ans) && ans.size() < stdAns.size()) {
return "partially correct";
}
// 完全错误:答案包含错误答案或未作答
return false;
case FILL_IN_THE_BLANKS:
// 完全正确:答案与标准答案内容完全匹配
if (answer.equalsIgnoreCase(givenAnswer.trim())) {
return true;
}
// 部分正确:答案包含部分正确答案且不含错误字符
if (isPartiallyCorrect(givenAnswer.trim())) {
return "partially correct";
}
// 完全错误:答案包含一个错误字符或完全没有答案
return false;
default:
throw new IllegalStateException("Unexpected value: " + type);
}
}
对方法
checkAnswer中每个部分的详细解释:
单选题(SINGLE_CHOICE):
使用 equals 方法比较标准答案和给定答案(去除空格后)。如果完全匹配,返回 true;否则返回 false。
多选题(MULTIPLE_CHOICE):
将标准答案和给定答案都分割成单词,并存储在 HashSet 中,以便进行集合操作。
完全正确:如果给定答案集合包含标准答案集合中的所有元素,并且两者的大小相同,则返回 true。
部分正确:如果给定答案集合包含标准答案集合中的所有元素,但给定答案的大小小于标准答案的大小(意味着用户没有选择所有正确答案,但也没有选择任何错误答案),则返回 "partially correct"。
完全错误:在其他情况下(即给定答案包含错误答案或未作答),返回 false。
填空题(FILL_IN_THE_BLANKS):
使用 equalsIgnoreCase 方法比较标准答案和给定答案(去除空格后)。如果完全匹配,返回 true。
使用 isPartiallyCorrect 方法检查部分正确性。如果给定答案包含标准答案中的部分字符(忽略空格和大小写),则返回 "partially correct"。
在其他情况下(即给定答案与标准答案不匹配,并且也不是部分正确),返回 false。
默认情况:
如果问题的类型不是预期的值之一,则抛出 IllegalStateException。
QuestionBank 类
- 功能:
存储一系列问题,并通过问题ID进行索引。
支持添加、删除、查询问题等功能。 - 方法:
| 成员变量 | 使用 LinkedHashMap 来存储问题,以保持插入顺序 |
|---|---|
| addQuestion(Question question) | 向题库中添加一个问题 |
| getQuestionById(int id) | 根据问题ID获取问题 |
| containsQuestion(int id) | 检查题库中是否存在某个问题ID |
| deleteQuestion(int id) | 删除某个问题 |
Map 接口
使用了 Map 接口提供的方法,如 put、get、containsKey 和 remove,来操作问题对象。
泛型
Map<Integer, Question> 使用了泛型来指定键和值的类型,这有助于类型安全。
集合框架
使用了 Java 集合框架中的 Map 接口和 LinkedHashMap 实现类。
Paper 类
- 功能:
表示一份试卷,包含多个问题及其对应的分数。
支持添加、查询试卷信息,计算试卷总分等功能。 - 方法:
| 成员变量:使用 HashMap 来存储试卷信息,其中键是试卷ID,值是一个 Map,该 Map 的键是问题ID,值是分数。 |
|---|
| addPaper(int paperId, int questionId, int score):向试卷中添加一个问题及其分数。 |
| getPaper(int paperId):根据试卷ID获取试卷信息。 |
| getTotalScore(int paperId):计算给定试卷的总分。 |
| containsPaper(int paperId):检查是否存在某个试卷ID。 |
| getAllQuestionIds(int paperId):获取试卷上的所有题目ID。 |
| getScoreForQuestion(int paperId, int questionId):根据试卷ID和题目ID获取相应的分数。 |
输入格式:试卷信息为独行输入,一行为一张试卷,多张卷可分多行输入数据。
为了使每一张试卷id映射唯一 一张试卷,故使用Map<Integer, Map<Integer, Integer>> 使用嵌套映射(nested maps)的方式来组织数据的结构,外层 Map 的键是试卷的 ID (paperId),值是一个内层的 Map,这个内层 Map 的键是问题的 ID (questionId),值是问题的分数 (score)。这样可通过试卷id可获取该试卷上的所有题目的分数。
Studentinformation 类
- 功能:
存储学生的学号及其对应的姓名。
支持添加、查询学生信息等功能。 - 方法:
| 成员变量:使用 HashMap 来存储学生的学号及其姓名。 |
|---|
| addStudent(String id, String name):向学生信息表中添加一条记录。 |
| getName(String id):根据学号获取学生的姓名。 |
| containsStudent(String id):检查学生信息表中是否存在某个学号 |
AnswerPaper 类
- 功能:
存储学生的答卷信息,包括试卷ID、学生学号及其回答的问题列表。
支持添加、查询学生答卷信息等功能。 - 方法:
| 成员变量:使用 HashMap 来存储学生的答卷信息,其中键是试卷ID,值是一个 Map,该 Map 的键是学生的学号,值是一个问题答案列表。 |
|---|
| addAnswerSheet(int paperId, String studentId, List |
| getAnswerSheet(int paperId):根据试卷ID获取所有学生的答卷记录。 |
| 嵌套类:Answer 用于表示单个答案,包含题目序号和答案内容。 |
final Map<Integer, Map<String, List<Answer>>> answerSheets = new HashMap<>();
这个成员变量是一个三层嵌套的 Map 结构:
第一层 Map 的键是 int 类型,表示试卷的 ID (paperId)。
第二层 Map 的键是 String 类型,表示学生的 ID (studentId)。
第二层 Map 的值是 List
DeletionQuestion 类
- 功能:
记录已被删除的问题ID。
支持标记删除问题和检查问题是否已被删除。 - 方法:
| 成员变量:使用 HashSet 来存储已删除的问题ID。 |
|---|
| markDeleted(int questionId):标记某个问题已被删除。 |
| isDeleted(int questionId):检查某个问题是否已被删除。 |
private final Set<Integer> deletedQuestions = new HashSet<>();
这个成员变量是一个 Set,用于存储已被删除的问题 ID。
使用 HashSet 作为实现类,这是因为 HashSet 提供了平均时间复杂度为 O(1) 的快速查找、添加和删除操作。
deletedQuestions 是 final 类型的,这意味着集合本身不能被重新赋值,但集合内的元素是可以被添加和删除的。
输入处理 (Input 类)
- 功能:
处理从标准输入读取的数据行,并根据行的前缀调用相应的方法来处理这些行。 - 方法:
| 构造函数:接收题库、试卷、学生信息、答卷以及删除题目的实例。 |
|---|
| validateInputFormat_1(String line):验证题目输入行的格式是否正确。 |
| validateInputFormat_2(String line):验证试卷输入行的格式是否正确。 |
| handleInput(String line):根据输入行的前缀处理不同的输入类型。 |
- 输入行处理逻辑:
| 题目输入:以#N:开头,格式为#N:问题ID #Q:问题内容 #A:答案。 |
| 试卷输入:以#T:开头,格式为#T:试卷ID 问题ID-分数 问题ID-分数 ...,并检查试卷总分是否为100。 |
| 学生信息输入:以#X:开头,格式为#X:学号-姓名 学号-姓名 ...。 |
| 答卷输入:以#S:开头,格式为#S:试卷ID 学号 #A:问题序号-答案内容 #A:问题序号-答案内容 ...。 |
| 删除题目输入:以#D:N-开头,格式为#D:N-问题ID。 |
输入类中正则表达式部分的详细解析
使用正则表达是为了验证输入行(题目信息,试卷信息行)的格式是否符合预期的标准,符合预期则返回true,不符合则返回false且输入的该行无效丢弃,不做输入处理。
validateInputFormat_1
用于验证题目输入行的格式。
确保输入行以 #N: 开头,并且包含题目 ID、问题内容(包含数字加法运算)和答案。
validateInputFormat_2
用于验证试卷输入行的格式。
确保输入行以 #T: 开头,并且包含试卷 ID 和一系列问题及其分数。
点击查看代码
public static boolean validateInputFormat_1(String line) {
// 正则表达式用来验证输入行的格式
String regex = "^#N:\\d+(\\s+#Q:\\d+\\+\\d+=\\s+#A:\\d+)$";
return line.matches(regex);
}
格式说明:
^#N::行开始位置 ^ 后跟 #N:,表示该行为题目信息。
\d+:一个或多个数字,表示题目 ID。
(\s+#Q:\d+\+\d+=\s+#A:\d+):空格 \s+,后跟 #Q: 和一个或多个数字,表示问题内容中包含数字加法运算,然后是 =,接着是空格 \s+ 和 #A:,最后是一个或多个数字,表示答案。
$:行结束位置。
点击查看代码
public static boolean validateInputFormat_2(String line) {
// 正则表达式用来验证输入行的格式
String regex = "^#T:\\d+(\\s+\\d+-\\d+)+$";
return line.matches(regex);
}
格式说明:
^#T::行开始位置 ^ 后跟 #T:,表示该行为试卷信息。
\d+:一个或多个数字,表示试卷 ID。
(\s+\d+-\d+)+:空格 \s+,后跟一个或多个数字 - 一个或多个数字,表示问题及其分数,这样的组合出现一次或多次 +。
$:行结束位置。
输出处理 (Output 类)
- 功能:
根据已处理的数据输出考试结果。 - 方法:
| 构造函数:接收题库、试卷、学生信息、答卷以及删除题目的实例。 |
| processOutput():遍历所有答卷记录,输出每个学生的得分情况。 |
这段代码是整段代码中最核心的部分,用于处理输出逻辑,包括根据学生的答卷情况计算成绩,并输出最终的成绩信息。
processOutput()方法处理过程:
1.遍历答卷:
- 遍历 answerPaper 中的所有答卷记录。
- 每个记录是一个键值对,键是试卷 ID (paperId),值是一个映射关系,键是学生 ID (studentId),值是该学生的答案列表。
2.检查试卷是否存在:
- 使用 paper.containsPaper(paperId) 方法检查是否存在给定的试卷 ID。
3.处理每个学生的答案:
- 遍历每个学生的答案列表。
- 获取学生 ID 和名字。
- 计算总分,并构建分数字符串。
4.处理每个问题的答案:
- 获取试卷上的所有问题 ID 序列。
- 对每个问题进行处理:
-
- 检查学生的答案。
-
- 检查问题是否已被删除。
-
- 检查问题是否存在。
-
- 判断答案是否正确,并计算分数。
-
- 输出问题内容、答案内容及是否正确,并累加分数。
5.输出最终成绩信息:
- 如果学生的名字存在,则输出学生 ID、名字、各题得分和总分。
- 如果学生的名字不存在,则输出学生 ID 未找到的消息。
主类 (Main 类)
- 功能:
读取标准输入,并根据输入处理逻辑进行处理。
处理完成后输出结果。 - 方法:
主函数:从标准输入读取数据直到遇到“end”,然后调用输出处理方法。 - 输入输出流程:
| 读取输入:从标准输入读取数据行。 |
| 处理输入:根据输入行的前缀调用相应的处理方法。 |
| 题目处理:将题目添加到题库中。 |
| 试卷处理:将问题及其分数添加到试卷中,并检查总分是否为100。 |
| 学生信息处理:将学生信息添加到学生信息表中。 |
| 答卷处理:将学生的答案添加到答卷记录中。 |
| 删除题目处理:标记题目为已删除。 |
| 输出处理:遍历所有答卷记录,并输出每个学生的得分情况。 |
2.第五次大作业
概括:这次大作业又来了一个全新设计, 家居强电电路模拟程序设计。第一次迭代难度不是很大,差一分就拿到满分了。之所以简单,是因为电路结构变化:只有一条线路,所有元件串联,处理起来相对简单。此次家居强电电路模拟程序-1使用了面向对象的设计思想,定义了多个类来表示智能家居系统中的不同设备(如开关、分档调速器、连续调速器、白炽灯、日光灯和吊扇)以及一个基类Device。通过继承和多态性,实现了不同设备之间的共同特性和差异特性的抽象与表达。
家居强电电路模拟程序-1
类图:

顺序图

设计分析
1.接口设计:
定义了一个Controllable接口,用于表示可以控制的设备。实现了该接口的类需要提供一个control方法来处理控制命令。
public interface Controllable {
void control(String command); // 处理控制命令
}
这样的设计使得系统能够灵活地添加新的可控设备,而无需修改现有的代码结构。
2.设备连接与处理:
使用PinandDevice类来处理设备的连接信息和控制命令。该类维护了两个映射:devices用于存储所有设备,connections用于存储设备之间的连接关系。提供了processConnection方法来处理设备连接信息,以及processControlCommand方法来处理控制命令。
3.电压更新与状态输出:
每个设备都有一个updateOutputVoltage方法,用于根据设备的特性和输入电压来计算输出电压PinandDevice类中的updateVoltages方法会遍历所有设备,根据连接关系更新设备的输入电压,updateOutputVoltage方法来更新输出电压。提供了printDeviceStatus方法来打印所有设备的当前状态。
代码思路
Device (抽象父类 )
- 功能:
定义了所有设备的公共属性和方法,如设备标识、输入电压、输出电压等。 - 属性:
| id:设备的唯一标识符。 |
| inputVoltage:输入电压。 |
| outputVoltage:输出电压。 |
| resistance:电阻。 |
- 方法:
| updateOutputVoltage():抽象方法,由子类实现具体的更新输出电压的逻辑。 |
| getOutputVoltage():获取输出电压。 |
| getId():获取设备标识。 |
| setInputVoltage(double inputVoltage):设置输入电压。 |
注:定义基类Device:
包含设备标识(id)、输入电压(inputVoltage)、输出电压(outputVoltage)和电阻(resistance)等属性。
提供了构造方法、更新输出电压的抽象方法(由子类实现)、获取输出电压和设备标识的方法,以及设置输入电压的方法。
控制设备 (继承Device 类 )
Switch:开关设备,可以切换状态(开/关)。
- 属性:state,表示开关状态(0为打开,1为关闭)。
- 方法:
| updateOutputVoltage():根据开关状态更新输出电压。 |
| control(String command):处理控制命令,切换开关状态。 |
| toString():返回设备状态的字符串表示。 |
GearSpeedRegulator:分档调速器,可以调节档位。
- 属性:gear,表示档位(0-3)。
- 方法:
| updateOutputVoltage():根据档位更新输出电压。 |
| control(String command):处理控制命令,调整档位。 |
| toString():返回设备状态的字符串表示。 |
ContinuousSpeedRegulator:连续调速器,可以调节连续档位。
- 属性:Gearposition,表示档位(0.00-1.00)。
- 方法:
| updateOutputVoltage():根据档位更新输出电压。 |
| control(String command):处理控制命令,调整档位。 |
| toString():返回设备状态的字符串表示。 |
受控设备 (继承Device 类 )
IncandescentLamp:白炽灯,可以根据电压差调节亮度。
属性:brightness,表示亮度(0-200lux)。
方法:
updateOutputVoltage():根据电压差更新亮度。
toString():返回设备状态的字符串表示。
FluorescentLight:日光灯,可以根据电压差调节亮度。
属性:brightness,表示亮度(0或180lux)。
方法:
updateOutputVoltage():根据电压差更新亮度。
toString():返回设备状态的字符串表示。
CeilingFan:吊扇,可以根据电压差调节转速。
属性:speed,表示转速(0-360转/分钟)。
方法:
updateOutputVoltage():根据电压差更新转速。
toString():返回设备状态的字符串表示。
每个设备类都继承自Device并实现了Controllable接口(如果可控)。
实现了各自的updateOutputVoltage方法和control方法(如果适用)。
提供了toString方法来返回设备的当前状态
PinandDevice(设备管理类)
- 作用:管理所有设备及其连接关系,处理控制命令,更新设备电压,打印设备状态。
- 属性:
devices:存储所有设备的 Map。
connections:存储设备连接关系的 Map。 - 方法:
| processConnection(String line):处理连接信息,创建设备并设置连接关系。 |
| createDevice(String deviceId):根据设备标识创建相应类型的设备。 |
| processControlCommand(String line):处理控制命令,调用相应设备的 control 方法。 |
| updateVoltages():更新所有设备的电压。 |
| printDeviceStatus():按类型顺序打印所有设备的状态。 |
| getTypeOrder(Device device):获取设备类型的顺序,用于排序。) |
注:这是代码核心部分,主要处理逻辑就在这段代码当中,下面我将详细介绍这部分
- 数据存储:
使用 LinkedHashMap 存储设备和连接关系,保持了插入顺序,在后面处理过程需要从首部开始向后面进行处理。
连接关系存储在 String 到 String 的映射中
private static Map<String, Device> devices = new LinkedHashMap <>(); // 存储所有设备
private static Map<String, String> connections = new LinkedHashMap <>(); // 存储设备连接关系
- 处理思路:
1.首先进行连接处理,通过引脚将设备连接起来,使之构成一条串联电路
2. 然后控制命令处理,通过控制命令处理,更新电路上的控制设备状态
3.再然后电压更新,对电路上的设备电压进行更新后,根据设备上的电压确定工作状态
4.最后设备状态打印,根据要求按顺序输出状态
sortedDevices.sort((d1, d2) -> {
int typeOrder1 = getTypeOrder(d1);
int typeOrder2 = getTypeOrder(d2);
if (typeOrder1 != typeOrder2) {
return typeOrder1 - typeOrder2;
}
return Integer.compare(Integer.parseInt(d1.getId().substring(1)), Integer.parseInt(d2.getId().substring(1)));
});
排序逻辑:
首先,对于集合中的两个设备d1和d2,通过调用getTypeOrder方法获取它们的类型顺序(typeOrder1和typeOrder2)。
如果这两个设备的类型顺序不同(typeOrder1 != typeOrder2),则根据它们的类型顺序进行排序。具体地,返回typeOrder1 - typeOrder2的结果,这意味着类型顺序较小的设备会被排在前面。
如果两个设备的类型顺序相同,则进一步比较它们的ID。这里假设ID是一个字符串,且从第二个字符开始(d1.getId().substring(1)和d2.getId().substring(1))是一个可以解析为整数的部分。这两个整数通过Integer.compare方法进行比较,确保ID数值较小的设备排在前面。
inputHandle(输入处理类 )
- 作用:处理用户输入的连接信息和控制命令。
- 方法:
input():读取用户输入,调用 PinandDevice 的方法处理连接信息和控制命令。
主类 Main
- 作用:程序入口,创建 inputHandle 对象处理用户输入,创建 PinandDevice 对象更新设备电压并打印设备状态。
3.第六次大作业
概括:这次是家居强电电路模拟程序的第二次迭代。第一次迭代只有一条串联电路,此次迭代增加了并联电路,就是串联电路上接了并联电路。受控设备增加了落地扇。
家居强电电路模拟程序-2
类图

顺序图

设计分析
此次难度大大加大,第一次迭代我是通过引脚来更新电压,由于此次引脚外接多个引脚,通过引脚来更新设备状态不可取(纵使通过输入引脚可获得设备输入电压,但是输出电压我们不知道,无法计算出电势差)。我花了足足一个上午的时间,才想到了思路。
先从总电路着手,我把并联电路看成受控设备接在总电路上。由于总电路是一条串联电路,把这一天单独拿出来处理就简单很多了。首先计算出总电路的电阻,然后计算出总电路电流,得到电流后,按顺序处理总电路上的设备。遍历总电路上的每个设备,用电流乘上对应电阻得到设备电压后,更新设备状态。由于我将并联电路看出来一个设备,故可得到并联电路上的电压。将并联电路上的子电路看出一天天串联电路,保存在List集合中,跟总电路处理逻辑一样,从list集合中拿出每条串联电路进行处理。这样操作之后,就更新了电路上每个设备状态。最后,按设备id顺序输出所有设备的状态或参数。
写完之后想想,真妙啊!这设计得就一个字:妙(我都想自己夸自己了😀)写了这么多次大作业,我发现设计是最重要的一环,代码编写是次要的。好的设计,代码写起来就有思路了,写起来就很快。
代码分析:
Device (抽象父类 )
- 功能:
定义了所有设备的公共属性和方法,如设备标识、输入电压、输出电压等。 - 属性:
| id:设备的唯一标识符。 |
| inputVoltage:输入电压。 |
| outputVoltage:输出电压。 |
| resistance:电阻。 |
- 方法:
| updateOutputVoltage():抽象方法,由子类实现具体的更新输出电压的逻辑。 |
| getOutputVoltage():获取输出电压。 |
| getId():获取设备标识。 |
| setInputVoltage(double inputVoltage):设置输入电压。 |
注:定义基类Device:
包含设备标识(id)、输入电压(inputVoltage)、输出电压(outputVoltage)和电阻(resistance)等属性。
提供了构造方法、更新输出电压的抽象方法(由子类实现)、获取输出电压和设备标识的方法,以及设置输入电压的方法。
控制设备 (继承Device 类 )
Switch:开关设备,可以切换状态(开/关)。
- 属性:state,表示开关状态(0为打开,1为关闭)。
- 方法:
| updateOutputVoltage():根据开关状态更新输出电压。 |
| control(String command):处理控制命令,切换开关状态。 |
| toString():返回设备状态的字符串表示。 |
GearSpeedRegulator:分档调速器,可以调节档位。
- 属性:gear,表示档位(0-3)。
- 方法:
| updateOutputVoltage():根据档位更新输出电压。 |
| control(String command):处理控制命令,调整档位。 |
| toString():返回设备状态的字符串表示。 |
ContinuousSpeedRegulator:连续调速器,可以调节连续档位。
- 属性:Gearposition,表示档位(0.00-1.00)。
- 方法:
| updateOutputVoltage():根据档位更新输出电压。 |
| control(String command):处理控制命令,调整档位。 |
| toString():返回设备状态的字符串表示。 |
受控设备 (继承Device 类 )
IncandescentLamp:白炽灯,可以根据电压差调节亮度。
- 属性:brightness,表示亮度(0-200lux)。
- 方法:
updateOutputVoltage():根据电压差更新亮度。
toString():返回设备状态的字符串表示。
FluorescentLight:日光灯,可以根据电压差调节亮度。
- 属性:brightness,表示亮度(0或180lux)。
- 方法:
updateOutputVoltage():根据电压差更新亮度。
toString():返回设备状态的字符串表示。
CeilingFan:吊扇,可以根据电压差调节转速。
- 属性:speed,表示转速(0-360转/分钟)。
- 方法:
updateOutputVoltage():根据电压差更新转速。
toString():返回设备状态的字符串表示。
FloorFan:落地扇,可以根据电压差调节转速。
- 属性:speed,表示转速(0-360转/分钟)。
- 方法:
updateVoltage():根据电压差更新转速。
toString():返回设备状态的字符串表示。
电路管理类
SeriesCircuit:串联电路类,管理串联电路中的设备。
- 属性:
| id:串联电路的唯一标识符。 |
| resistance:串联电路的总电阻。 |
| connections:存储设备连接关系的 Map。 |
| devices:存储串联电路中的设备的 Map。 |
| current:流经串联电路的电流。 |
| inputVoltage:输入电压。 |
| outputVoltage:输出电压。 |
- 方法:
| setInputVoltage(double inputVoltage):设置输入电压。 |
| total_resistance():计算串联电路的总电阻。 |
| getResistance():获取总电阻。 |
| setCurrent(double current):设置电流。 |
| getCurrent():获取电流。 |
| getConnections():获取连接关系。 |
| getDevices():获取设备。 |
ParallelCircuit:并联电路类,管理并联电路中的设备。
- 属性:
circuits:存储并联电路中的串联电路的 List。 - 方法:
| total_resistance():计算并联电路的总电阻。 |
| getCircuits():获取串联电路。 |
| updateVoltage():更新并联电路中的设备状态。 |
PinandDevice(设备管理类 )
- 作用:管理所有设备及其连接关系,处理控制命令,更新设备电压,打印设备状态。
- 属性:
| SeriesCircuitsheet:存储所有串联电路的 Map。 |
| ParallelCircuitsheet:存储所有并联电路的 Map。 |
| ALLdevices:存储所有设备的 Map。 |
- 方法:
| processConnection_S(String line):处理串联电路的连接信息。 |
| processConnection_p(String line):处理并联电路的连接信息。 |
| createDevice(String deviceId, Map<String, Device> devices):根据设备标识创建相应类型的设备。 |
| processControlCommand(String line):处理控制命令,调用相应设备的 control 方法。 |
| Update_circuit_resistance():更新电路的电阻和电流。 |
| printDeviceStatus():按类型顺序打印所有设备的状态。 |
| getTypeOrder(Device device):获取设备类型的顺序,用于排序。 |
注:此部分代码是主要部分,我将对逻辑处理思路进行分析
SeriesCircuitsheet:存储所有串联电路对象的 Map。
ParallelCircuitsheet:存储所有并联电路对象的 Map。
ALLdevices:存储所有设备对象的 Map。
private Map<String,SeriesCircuit> SeriesCircuitsheet = new LinkedHashMap<>();//存储串联电路对象集合
private Map<String,ParallelCircuit> ParallelCircuitsheet = new LinkedHashMap<>();//存储并联电路对象集合
private Map<String, Device> ALLdevices = new LinkedHashMap<>();//电路中所有设备对象
- 在processConnection_p方法中,处理并联电路的连接信息,将串联电路对象添加到并联电路中,使用List集合接口存储串联电路对象
private List<SeriesCircuit> circuits = new LinkedList<>();//并联电路中存储了串联电路#M1[T1 T2] - 在Update_circuit_resistance()中更新电路的电阻和电流。首先遍历所有并联电路对象,
total_resistance方法计算每个并联电路的总电阻,然后使用 List 进行倒序遍历 SeriesCircuitsheet,再然后调用updateVoltage方法更新每个设备的输出电压。根据输入电压和总电阻计算总电路电流。最后调用printDeviceStatus()根据总电路电流和设备电阻更新每个设备的电压和状态。 - 短路与断路处理:
使用-1表示电阻无穷大,代表电路断开断路。如果算得并联电路电压为0,则表示短路。在并联电路中,用电压为0处理每一个设备,得到的自然就是未工作状态
if(circuit.total_resistance()!=0) {
if(circuit.total_resistance()==-1)
continue;
inputHandle(输入处理类)
- 作用:处理用户输入的连接信息和控制命令。
- 方法:
input():读取用户输入,调用 PinandDevice 的方法处理连接信息和控制命令。
主类 Main
- 作用:程序入口,创建 inputHandle 对象处理用户输入,创建 PinandDevice 对象更新设备电压并打印设备状态。
- 方法:
main(String[] args):程序主方法。
三、采坑心得
1.写答题判题程序最后一次迭代时,未注意到题目要求使用到继承。由于未注意到此要求,我最开始没有用到继承。我将代码提交到pta后,最后几个测试点实在改不出来就放弃了。后来,经同学提醒,此次大作业要使用到继承,没用到继承,一律0分。我晕,pta是以提交的最高分那份代码为最终代码,这么说来,不光要修改代码,我还得提高自己的分数来更新最终代码。于是,我开始调啊调啊,最后几个测试点就是找不出来。无奈,我上博客园搜别人写得大作业总结,企图找到有人跟我一样,卡在那几个测试点上。功夫不负有心人,火眼金睛的我终于找到了!按他的解决方法,一直没过测试点终于过了。避免了0分的悲剧。哎~下次写之前我一定要好好看清楚要求。
2.在写家居强电电路模拟程序-1中,我是以引脚来更新设备状态的,设备的输出引脚接下一个设备的输入引脚。以上一个设备输出引脚的输出电平作为设备的输入电压。由于第一次迭代只有一条电路,电路上只有控制与受控设备。设计的时候,我想,改变电压的设备不可能接GND,受控设备就只能接GND了。这就导致了我有几个测试点一直没过(开关也有可能接GND),这么说,你可能还理解不了,下面这个例子可以说明:
把开关接GND后,由于我默认受控设备接GND(默认输出电压为0),按照我的设计思路,D2引脚接了VCC,那么输入电压为220v,这样在D2上算出来的工作电压为220v,工作状态自然就为满幅输出了

作业截至的时候,不知不觉才发现开关也有可能接GND啊。悔不当初啊!要是早想到就好了,这样就可以拿到满分了/(ㄒoㄒ)/~~悲剧啊!
3.家居强电电路模拟程序-2主要就是设计问题。设计好了,就简单许多。开始最初我想延续第一次的思路,可是发现不行。因为设备的输出引脚可能接多个输入引脚(这次考虑并联),这样就不好处理了。于是,我花了差不多三个小时设计思路,草稿本上画了又画,还是老师上课的时候就此题提了一嘴,可以通过计算电流或者采用分压法来获得设备的工作电压。我采用了前者,通过电流,就算出电压。
此次大作业,我犯了一个致命错误,话不多说,直接看代码。

在case'A'分支中,我竟然未加break语句!导致了代码继续执行 case 'M' 分支的代码,结果就是设备对象被覆盖,原本在 case 'A' 分支中创建的 FloorFan 对象会被 case 'M'中 ParallelCircuitsheet.get(deviceId) 返回的对象覆盖,简而言之就是设备A未创建,设备A的实例为空!编译是没任何问题的,我怀着自信提交代码,本以为可以拿到较高分数,没想到,只有6分!6分啊!其余测试点全是非零返回。由于此次测试点没有提示,我一直找不到哪里出现错误。也怪我自己,在测试代码的时候,没有在测试样例中加上设备A。故事的最后,感谢同学送来的堪称神之一手的测试样例,他的测试样例包含了设备A。我将他的样例一输入,代码里面就报错,设备A实例为null,为创建。经过调试,才找到这个致命问题。感谢同学!感谢上天!
四、改进建议
1.家居强电电路模拟程序-1
类命名和职责:
重命名 PinandDevice 类为更直观的名称,如 CircuitManager。
考虑将连接处理、控制命令处理和电压更新等功能拆分为独立的类或模块。
连接处理:
改进 processConnection 方法,确保正确处理所有引脚连接,并考虑 GND 引脚。
使用更灵活的数据结构来存储连接关系,如 Map<String, List
控制命令处理:
考虑使用命令模式或策略模式来处理不同类型的控制命令。
在 Device 类中添加一个方法来检查设备是否支持特定的控制命令。
电压更新:
考虑使用更复杂的电路模拟算法来更新电压,特别是在处理复杂电路时。
错误处理:
添加错误处理逻辑,如解析连接信息时的异常处理。
2..这三次大作业使用到了大量的正则表达式匹配,有的复杂一点的匹配算法是借助ai完成的,运用正则表达式并不熟练。在课后,需要去更多的了解有关正则的方式匹配。
3.我发现我的代码重复性太高,一段代码,多次出现,代码冗余严重。在今后的大作业中应避免该问题,减少代码冗余并提高代码复用性,通过使用设备工厂类和提取公共逻辑,让代码变得更加简洁和易于维护。同时,增加异常处理和日志记录,提高了代码的健壮性和可调试性。
五、总结
这三次大作业写完,我发现会写代码不是主要的,会设计才是重要的一环(当然,这是本人的看法)。良好的 Java 设计不仅仅是编写功能正确的代码,更重要的是确保代码的可维护性、可读性、可重用性、性能、安全性和可扩展性。仅有编写代码的能力是不够的。设计在Java开发中同样至关重要。就比如这次的家居强电电路模拟程序-2,设计好了,你才知道怎么下手去写。没设计好,就开始写,纵使写到最后,也是一包渣。写家居强电电路模拟程序-2的时候,我花了大量时间去设计,去构思该如何去写,要设计成怎么的电路,处理控制设备的命令后要怎么去更新受控设备的电压及状态。这些时间花掉是值得的,代码第一次提交就拿到了91分(不考虑上述我犯的致命错误)。总而言之,设计至关重要,一个良好的设计能够使系统更加清晰、易于维护,并且能够更好地适应未来的变化。
这三次大作业都使用到了继承与多态。可见其重要性。综合以前几次大作业,java的三个重要思想:封装,继承,多态也算是都深入了解并使用到了。通过使用继承与多态这两个面向对象编程的核心概念,代码复用大大提高了。继承允许我们创建一个新的类(子类)来继承一个已存在的类(父类)的属性和方法。这样,子类就可以复用父类的代码,避免了重复编写相同的代码,提高了开发效率。多态性允许我们使用父类类型的引用来指向子类对象。这样,我们可以在运行时动态地决定调用哪个类的方法,实现了接口的多种不同表现形式或行为。多态性增强了程序的灵活性和可扩展性。
学习到了抽象类和接口的使用,抽象类是用关键字abstract修饰的类,它不能被实例化。抽象类中可以包含抽象方法(没有方法体的方法,用abstract修饰),通过子类重写抽象方法,从而达到多态,提高代码的灵活性。接口通常用于定义一组相关的行为或功能,这些行为或功能可以由不同的类来实现。在家居强电电路模拟程序中我定义了处理控制命令接口。接口中有void control(String command)方法。通过三个控制设备实现接口,重写接口中方法,从而达到多态,提高代码的灵活性。总而言之,抽象类和接口在Java编程中都具有重要的作用,它们各自具有独特的特性和使用场景。


浙公网安备 33010602011771号