OOP-大作业总结1
一、前言
1.三次题目集所考察知识点
-
scanner类的运用
-
单列集合和双列集合的运用
-
如何用正则表达式对字符串处理
-
自定义类的运用
2.题目集题量
-
每个题目集的题量差不多要花一周的时间去完成
3.题目集难度
-
每个题目集的前两道难度适中
-
每个题目集最后一题偏难,需要花费较多时间去思考
二、设计与分析
1.答题判题程序-1
第一道答题判题程序比较基础,输入的信息只有三部分,题目数量手动输入,题目按固定格式输入,答案的顺序默认与试卷上的题目顺序对应,与题号无关。主要难点在于学会用正则表达式将#N、#T、#A的字符区分开来并分别存储里面我们想要的数据,只需定义Question类、Paper类、AnswerSheet类即可,为后续的答题判题程序打下基础。
UML类图如下

复杂度分析
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| com.NCHU.PTA2.AnswerSheet.AnswerSheet(Paper) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.AnswerSheet.outputQuestion() | 5 | 1 | 4 | 4 |
| com.NCHU.PTA2.AnswerSheet.saveAnswer(String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.AnswerSheet.saveResult() | 4 | 1 | 3 | 3 |
| com.NCHU.PTA2.Main.main(String[]) | 3 | 2 | 3 | 4 |
| com.NCHU.PTA2.Paper.Paper() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Paper.Paper(int, Map<Integer, Question>) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Paper.getQuestionCount() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Paper.getQuestionMap() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Paper.saveQuestion(int, Question) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Paper.setQuestionCount(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Paper.setQuestionMap(Map<Integer, Question>) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Question.Question(int, String, String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Question.getContent() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Question.getNumber() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Question.getStandardAnswer() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Question.setContent(String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Question.setNumber(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA2.Question.setStandardAnswer(String) | 0 | 1 | 1 | 1 |
| Class | OCavg | OCmax | WMC | |
| com.NCHU.PTA2.AnswerSheet | 2.25 | 4 | 9 | |
| com.NCHU.PTA2.Main | 4 | 4 | 4 | |
| com.NCHU.PTA2.Paper | 1 | 1 | 7 | |
| com.NCHU.PTA2.Question | 1 | 1 | 7 |
2.答题判题程序-2
答题判题程序2在1的基础上增加了一些功能;
- 三种信息的输入可以打乱
- 一行为一张试卷,可以输入多行(即多张试卷)
- 每张试卷有相应的试卷编号和每题的赋分情况
- 答卷上有相应试卷的编号,答案的顺序与试卷题目顺序相对应,多出的答案无效,若缺少答案则记为0分且在打印结果时要输入"answer is null",
- 对试卷各题设置分数时,对分值未满100的试卷进行"alert: full score of test paper"+试卷号+" is not 100 points"提示
- 最后还要统计每题得分和总分
针对上述增加的功能做了如下设计和改进:
- 添加List集合用于存储不同信息的每行字符串,以便后续解析字符串信息
- 添加题库(questionBank)来存储所解析到的不同题目
- 为解决存在多张试卷且答卷可以根据不同试卷作答的问题,添加试卷库(paperBank),来存储不同编号的题目
- 添加User类,让试卷与答卷产生关联
设计概要:
答题判题程序-2的核心是如何设计类,让不同类之间产生关联,以User类为核心,其他的类依赖于它。同时还有字符串的解析,如何运用split()方法将字符串进行切割,将我们想要的数据分离出来进行存储。对答卷进行判断的时候调用saveResult()方法来判断答卷答案是否与相应题目的标准答案相等,最后通过printInfo()方法将用户的答题信息输出。
UML图

复杂度分析
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| com.NCHU.PTA3.AnswerSheet.AnswerSheet() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.AnswerSheet.AnswerSheet(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.AnswerSheet.getAnswerNumber() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.AnswerSheet.getInputAnswer() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.AnswerSheet.getUserID() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.AnswerSheet.saveAnswer(String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.AnswerSheet.setAnswerNumber(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.AnswerSheet.setInputAnswer(ArrayList |
0 | 1 | 1 | 1 |
| com.NCHU.PTA3.AnswerSheet.setUserID(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Main.main(String[]) | 43 | 8 | 24 | 25 |
| com.NCHU.PTA3.Paper.Paper() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Paper.Paper(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Paper.getPaperNumber() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Paper.getQuestionMap() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Paper.getScoreSetings() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Paper.saveQuestion(int, Question, int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Paper.setPaperNumber(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Paper.setQuestionMap(LinkedHashMap<Integer, Question>) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Paper.setScoreSetings(ArrayList |
0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Question.Question() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Question.Question(int, String, String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Question.getContent() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Question.getNumber() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Question.getStandardAnswer() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Question.setContent(String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Question.setNumber(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.Question.setStandardAnswer(String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.User() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.User(Paper, AnswerSheet) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.getAnswerSheet() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.getDeleteBankList() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.getMissingAnswerCount() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.getPaper() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.getResult() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.getScores() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.getUserBankMap() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.isPaperMatchAnswerSheet() | 2 | 2 | 1 | 2 |
| com.NCHU.PTA3.User.printInfo() | 12 | 3 | 8 | 8 |
| com.NCHU.PTA3.User.saveResult() | 7 | 3 | 5 | 5 |
| com.NCHU.PTA3.User.saveScore() | 10 | 3 | 6 | 6 |
| com.NCHU.PTA3.User.setAnswerSheet(AnswerSheet) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.setDeleteBankList(ArrayList |
0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.setMissingAnswerCount(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.setPaper(Paper) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.setResult(ArrayList |
0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.setScores(ArrayList |
0 | 1 | 1 | 1 |
| com.NCHU.PTA3.User.setUserBankMap(HashMap<Integer, String>) | 0 | 1 | 1 | 1 |
| Class | OCavg | OCmax | WMC | |
| com.NCHU.PTA3.AnswerSheet | 1 | 1 | 9 | |
| com.NCHU.PTA3.Main | 25 | 25 | 25 | |
| com.NCHU.PTA3.Paper | 1 | 1 | 9 | |
| com.NCHU.PTA3.Question | 1 | 1 | 8 | |
| com.NCHU.PTA3.User | 1.7 | 7 | 34 |
3.答题判题程序-3
答题判题程序-3在2的基础上新增了一些功能:
- 新增学生信息和删除信息这两种信息
- 答卷上不仅有试卷编号,还有学生学号(以便通过学号去找学生姓名),答案由题目在试卷上的顺序号和学生作答答案构成,同时还要考虑学生未作答的情况,
- 删除信息为一行,包含要删除题目的题目号
针对上述新增功能的设计与改进
- 新增集合用于存储学生信息和删除信息,以便后续解析。
- 针对新增功能2,在AnswerSheet类中添加inputAnswerSheetMap作为答题卡,键为题目顺序号,值为学生作答答案。这里要求顺序号有序,所以利用TreeMap集合可对键默认从小到大排序,后续答案的处理和该输出什么样的信息都依赖于这张答题卡上的值的信息。
- 新增List集合用于存储所要删除题目的题号,通过题目号找到题目的顺序号,若该题目答案不为空,则将答案换成"deleted",标记这道题被删除了,后续输出"The question +题号+ is invalid~0".
设计概要
答题判题程序-3的核心是如何根据答卷获取一张答题卡,下面将通过具体代码和测试样例来解释
处理答卷信息代码
//处理答卷信息
int studentCount = answerSheetString.size();
for (int i = 0; i < studentCount; i++) {
Student student = new Student(studentBank, deleteBank, questionBank);
studentsList.add(student);
}
for (int i = 0; i < answerSheetString.size(); i++) {
String[] str = answerSheetString.get(i).split("\\s*#[SA]:\\s*");
int answerCount = str.length - 2;
String[] str1 = str[1].split(" ");
int paperNumber = Integer.parseInt(str1[0]);
String studentID = str1[1];
studentsList.get(i).getPaper().setPaperNumber(paperNumber);
studentsList.get(i).getAnswerSheet().setStudentID(studentID);
//根据答卷上的试卷编号去找试卷库里找对应的试卷
Set<Map.Entry<Integer, Paper>> entries = paperBank.entrySet();
for (Map.Entry<Integer, Paper> entry : entries) {
if (paperNumber == entry.getKey()) {
studentsList.get(i).getAnswerSheet().setAnswerSheetMatchPaper(true);
studentsList.get(i).setPaper(entry.getValue());
break;
} else {
studentsList.get(i).getAnswerSheet().setAnswerSheetMatchPaper(false);
}
}
//如果学生答卷上的试卷编号引用正确,就继续存储学生输入的答案,否则直接不存储
if (studentsList.get(i).getAnswerSheet().isAnswerSheetMatchPaper()) {
//把答案存起来,按找顺序号从小到大存,
for (int j = 0; j < answerCount; j++) {
String[] str2 = str[j + 2].split("-");
int sequenceNumber = Integer.parseInt(str2[0]);
String inputAnswer = str2[1];
studentsList.get(i).getAnswerSheet().getInputAnswerMap().put(sequenceNumber, inputAnswer);
}
//如果没有这添加顺序号,并把inputAnswer赋为NoExist
int questionCount = studentsList.get(i).getPaper().getQuestionLinkedHashMap().size();
TreeMap<Integer, String> map = studentsList.get(i).getAnswerSheet().getInputAnswerMap();
for (int k = 1; k <= questionCount; k++) {
if (map.containsKey(k)) {
} else {
studentsList.get(i).getAnswerSheet().getInputAnswerMap().put(k, "NoExist");
}
}
}
}
测试样例
#N:1 #Q:1+1= #A:2
#N:2 #Q:2+2= #A:4
#T:1 1-5 3-2 2-5 6-9 4-10 7-3
#S:1 20201103 #A:2-5 #A:6-4
#D:N-2
#X:20201103 Tom-20201104 Jack
#S:2 20201103 #A:1-5 #A:2-4
#S:3 20201103 #A:1-5 #A:2-4
end
调试过程中的答题卡

运行结果
alert: full score of test paper1 is not 100 points
answer is null
non-existent question~0
answer is null
answer is null
answer is null
non-existent question~0
20201103 Tom: 0 0 0 0 0 0~0
The test paper number does not exist
The test paper number does not exist
这样就实现了答案按顺序号存储且未输入答案的题目用"NoExist"来代替,以便后续会结果的判断
此题的另一个难点在于什么时候该输出什么信息,即信息的输出是有有优先级的
信息输出优先级如下
- 答案不存在——>"answer is null"
- 答案存在的话
- 题目在题库中不存在——>"non-existent question~0"
- 题目在题库中存在
- 题目若被删除——>"The question +题号+ is invalid~0"
- 题目未被删除按正常格式输出——>"题目内容+作答答案+判题结果(true/false)"
打印信息方法源码
public void printInfo() {
deteteQuestion();
int questionCount = paper.getQuestionLinkedHashMap().size();
TreeMap<Integer, String> answerMap = answerSheet.getInputAnswerMap();
LinkedHashMap<Integer, Question> questionMap = paper.getQuestionLinkedHashMap();
for (int i = 1; i <= questionCount; i++) {
String content = answerMap.get(i);
//如果答案为空
if ("NoExist".equals(content)) {
System.out.println("answer is null");
scoreList.add(0);
} else {
//答案不为空的话,然后通过顺序号去找到试卷上对应的题号
int j = 1;
int questionNumber1 = -1;
Set<Integer> questionNumberSet = questionMap.keySet();
for (Integer questionNumberInSet : questionNumberSet) {
if (i == j) {
questionNumber1 = questionNumberInSet;
break;
}
j++;
}
//判断题目是否在题库中存在
if (questionBank.containsKey(questionNumber1)) {
//题目在题库中存在,然后检查题目是否被删除
if ("deleted".equals(content)) {
System.out.println("the question " + questionNumber1 + " invalid~0");
scoreList.add(0);
} else {
//接下来判断对错即可
boolean right = false;
if (questionBank.get(questionNumber1).getStandardAnswer().equals(content)) {
right = true;
} else {
right = false;
}
System.out.println(questionBank.get(questionNumber1).getQuestionContent()
+ "~" + content + "~" + Boolean.toString(right));
if (right) {
scoreList.add(paper.getScoreSettingList().get(i - 1));
} else {
scoreList.add(0);
}
}
} else {
//题目在题库中不存在
System.out.println("non-existent question~0");
scoreList.add(0);
}
}
}
String studentID = answerSheet.getStudentID();
if (studentBankMap.containsKey(studentID)) {
System.out.printf("%s %s: ", studentID, studentBankMap.get(studentID));
int size = scoreList.size();
int sum = 0;
for (int i = 0; i < size; i++) {
sum += scoreList.get(i);
}
for (int i = 0; i < size; i++) {
if (i < size - 1) {
System.out.printf("%d ", scoreList.get(i));
} else {
System.out.printf("%d~%d\n", scoreList.get(i), sum);
}
}
} else {
System.out.println(studentID + " not found");
}
}
UML图

复杂度分析
| Method | CogC | ev(G) | iv(G) | v(G) |
|---|---|---|---|---|
| com.NCHU.PTA4.AnswerSheet.AnswerSheet() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.AnswerSheet.AnswerSheet(String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.AnswerSheet.getInputAnswerMap() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.AnswerSheet.getStudentID() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.AnswerSheet.isAnswerSheetMatchPaper() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.AnswerSheet.setAnswerSheetMatchPaper(boolean) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.AnswerSheet.setInputAnswerMap(TreeMap<Integer, String>) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.AnswerSheet.setStudentID(String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Main.main(String[]) | 56 | 10 | 27 | 27 |
| com.NCHU.PTA4.Paper.Paper() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Paper.Paper(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Paper.getPaperNumber() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Paper.getQuestionLinkedHashMap() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Paper.getScoreSettingList() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Paper.saveQuestion(int, Question) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Paper.setPaperNumber(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Paper.setQuestionLinkedHashMap(LinkedHashMap<Integer, Question>) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Paper.setScoreSettingList(ArrayList |
0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Question.Question() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Question.Question(int, String, String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Question.getQuestionContent() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Question.getQuestionNumber() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Question.getStandardAnswer() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Question.setQuestionContent(String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Question.setQuestionNumber(int) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Question.setStandardAnswer(String) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.Student() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.Student(HashMap<String, String>, ArrayList |
0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.deteteQuestion() | 12 | 4 | 4 | 6 |
| com.NCHU.PTA4.Student.getAnswerSheet() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.getDeleteBankList() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.getPaper() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.getQuestionBank() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.getScoreList() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.getStudentBankMap() | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.printInfo() | 42 | 5 | 10 | 13 |
| com.NCHU.PTA4.Student.setAnswerSheet(AnswerSheet) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.setDeleteBankList(ArrayList |
0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.setPaper(Paper) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.setQuestionBank(HashMap<Integer, Question>) | 0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.setScoreList(ArrayList |
0 | 1 | 1 | 1 |
| com.NCHU.PTA4.Student.setStudentBankMap(HashMap<String, String>) | 0 | 1 | 1 | 1 |
| Class | OCavg | OCmax | WMC | |
| com.NCHU.PTA4.AnswerSheet | 1 | 1 | 8 | |
| com.NCHU.PTA4.Main | 27 | 27 | 27 | |
| com.NCHU.PTA4.Paper | 1 | 1 | 9 | |
| com.NCHU.PTA4.Question | 1 | 1 | 8 | |
| com.NCHU.PTA4.Student | 2.06 | 13 | 33 |
三、踩坑心得
- 写答题程序-1时,在Paper类中声明了questionMap用于存储题目,但在构造方法中并未对其进行初始化,为其开辟一块存储空间,造成NullPointerException,此后在声明除基本数据类型外的成员变量时要在构造方法中new 一个对象出来,让其有所指。
- 写答题判题程序-2的时候,每张试卷可以给不同题目设置分数,最开始在设计时把这个赋分设置设计成了题目的属性,在有两张试卷且都对某道题赋不同分数时后面赋的分会覆盖掉前面设置的分数,导致判题错误。每张试卷对不同题号的题目设置分数时,这个分数应该为试卷的属性,再Paper类中添加ArrayList集合来存储设置的分数,这样就可以达到想要的效果。
- 在写答题判题程序-3时,如果遇到答案为空的情况,将InputAnswerSheetMap中的值赋为"null"来表示答案为空的情况,但在 Java 中,字符串是一个对象类型,可以赋值为
null。赋值为null表示该字符串对象不再指向任何有效的字符串实例,即成为一个空引用。最终导致答案不存在的情况不被识别。
四、改进建议
答题判题程序根据增加的功能不断更新和优化。根据不同的功能需求设计合适的数据存储结构。在此程序中,题目、试卷、学生信息和删除信息都采用了Map集合进行存储,题目采用HashMap存储,键->题目编号,值->题目;试卷采用HashMap存储,键->试卷编号,值->试卷;学生信息采用HashMap存储,键->学号,值->学生姓名;因为将打印输出信息的方法写在Student类中,所以每次在初始化学生的时候,要将题目库、试卷库和学生库导入学生的属性值里面。这样会浪费存储空间。所以在设计时,可以将打印输出信息的方法写在Main()方法内部,达到节省空间的效果。同时在用正则表达式对输入信息进行格式判断时的正则表达式也可以用于后续不同信息字符串的切割,而不是采用分步切割的方式,这样可能对某些特殊字符串处理不了,导致不能通过PTA测试点。
五、总结
- 通过PTA大作业的练习,可以更加综合地运用所学知识,并且在练习的过程中还需要不断学习新知识点,这样大大增加了我们的自学能力,
- 由于大作业代码行数的提高,功能越来越多,在书写时要将各个功能分隔开。在设计时运用设计模式的知识点可以更好地帮助我们将代码写的更加规范合理。
- 在出错后不断调试的过程中,逐渐对代码内部逻辑有了更深刻的认识。

浙公网安备 33010602011771号