一、初识java
2023年9月入学nchu,大一上半学期我们1方向的都主修C语言,到了下学期,陡然转变为java
java语言确实脱胎于C语言,基础语法很相似,接受起来其实很容易,但是对于一个大学牲来说要学习的内容就变成了∞
- 关于java类与对象
其实从C语言的学习来看,面向过程的思想貌似有点根深蒂固,直到现在其实写java大作业我的一部分思路还是“输入--处理--输出”的过程
但与之不同的是,OOP是面向对象,C语言的思路并不符合java的写法。
类是一种模板或蓝图,描述了一组具有相同属性和行为的对象,能够通过定义一个类来创建多个具有相同结构但独立状态的对象。
学习如何在类中定义属性和方法,就可理解如何将实际问题映射为代码的过程,如何通过分解问题来设计类的结构。
- 关于java程序设计过程
在具体的学习中可知,程序内每一个对象都是一个类的实例化,这意味着程序中的每个实体都可以被归类和描述。在Java程序设计中,类是一种抽象的模板,它定义了对象的属性和行为
也就是说,相较于同一道题目,java要先抽离出题目涉及的类,及这个类的属性和行为,通过调用对象的行为(函数)来达到题目功能的实现。
而C语言,依旧是“输入--处理--输出”。
由此可见,无论在接下来的学习还是以后做实际项目,Java的面向对象的思想尤为重要!
二、java大作业(答题判题程序——3)整体设计
1 public class Main { 2 3 public static void main(String[] args) { 4 Scanner scanner = new Scanner(System.in); 5 LinkedList<Student> student = new LinkedList<>(); 6 LinkedList<AnswerPaper[]> answer = new LinkedList<>(); 7 LinkedList<Boolean> judgeToAnswer = new LinkedList<>(); 8 LinkedList<String[]> list = new LinkedList<>(); 9 } 10 } 11 12 class Tool { 13 public static void processingString(LinkedList<String[]> allInformation, String str) {} 14 15 public static void manageLinkQuestion(LinkedList<String[]> list) {} 16 17 public static void manage100points(String[] element) {} 18 19 public static void manageToStudent(LinkedList<String[]> list, LinkedList<Student> student) {} 20 21 public static void manageToDelete(LinkedList<String[]> list) {} 22 23 public static String[] find_S_Answer(LinkedList<String[]> list) {} 24 25 public static String[] find_T_TestPaper(LinkedList<String[]> list, String temp) {} 26 27 public static int manageIsOrNo(LinkedList<String[]> list) {} 28 29 public static void manageConsist(LinkedList<String[]> list, LinkedList<Student> student, 30 LinkedList<AnswerPaper[]> answer, LinkedList<Boolean> judgeToAnswer) {} 31 32 public static void judgmentQuestion(LinkedList<String[]> list, LinkedList<Student> student, 33 LinkedList<AnswerPaper[]> answer, LinkedList<Boolean> judgeToAnswer) {} 34 } 35 36 class AnswerPaper {// 答卷类,判题结果将在这里产生 37 private int num = 1; 38 private String student_Answer; 39 private int mark = 0; 40 private boolean judge = false; 41 private String studentID; 42 private String studentName; 43 private boolean key = true; 44 public static TestPaper allProblems = new TestPaper(); 45 46 public AnswerPaper(int num, String student_Answer, int mark, String studentID, String studentName) {} 47 48 public void setKey() {} 49 50 public String judgeToAnswer() {} 51 52 public int getMark() {} 53 54 public boolean matchIDandName() {} 55 56 public String getStudentID() {} 57 58 public void setStudentID(String studentID) {} 59 60 public String getStudentName() {} 61 62 public void setStudentName(String studentName) {} 63 64 public String get_A() {} 65 66 public void setNum(int num) {} 67 68 public AnswerPaper() {} 69 } 70 71 class TestPaper {// 试卷类,将存储所有题目, 72 private int num = 1; 73 public Topic[] questions; 74 75 public void setNum(int num) {} 76 77 public TestPaper() {} 78 79 public void set_Q_and_A(int i, String num, String question, String standardAnswer) {} 80 81 public String getTestPaperAnswer(int count) {} 82 83 public void deleteQuestion(String count) {} 84 85 public String getAllQuestion(int count) {} 86 } 87 88 class Topic {// 单独存储一个题目的类 89 private String num; 90 private String question; 91 private String standardAnswer; 92 private boolean condition = true; 93 94 public Topic() {} 95 96 public Topic(String num, String question, String standardAnswer) {} 97 98 public boolean getCondition() {} 99 public void setCondition(boolean condition) {} 100 101 public String getStandarAnswer() {} 102 103 public String getQuestion() {} 104 105 public String getNum() {} 106 107 } 108 109 class Student { 110 private String ID; 111 private String name; 112 113 public Student() {} 114 115 public Student(String ID, String name) {} 116 117 public String getID() {} 118 119 public void setID(String ID) {} 120 121 public String getName() {} 122 123 public void setName(String name) {} 124 125 }
- 类设计
对于题目所存在的各种输入,我都对应的设计了类,其中包括
题目类:负责存储单个题目的类,例如【#N:1 #Q:1+1= #A:2】
试卷类:用链表存储一系列试卷类的实例化对象,每个对象包含索引题号,分数,状态等
答卷类:用链表存储一系列答卷类的实例化对象,每个对象包含序号,学生答案,学生姓名学号,判题状态等
学生类:负责存储学生信息
工具类:负责处理数据流,拆分或组合至对应类里
主方法:处理输入,调用方法以完成题目
- 工具类的单独设计
为了更好的处理输入,处理验错,是否为满分卷等行为(这些行为并不是其他类应该负责的行为)
我单独创建工具类,并设所有方法为静态,将这些行为单独做出来不仅可以更容易调试代码,也可以有很好的逻辑性
- 数据流
将输入进来的每一字符串,都置于LinkedList<String[]> list = new LinkedList<>();中,边输入边进行拆分
如【#N:1 #Q:1+1= #A:2】,将被拆分成为String[]类型,为【N,1,1+1=,2】形式
拆分完成后,使用manageLinkQuestion(LinkedList<String[]> list)函数,处理所有的N问题,摘出所有的N问题,并为每个创建Topic类的对象,并存储
在答卷类里包含public static TestPaper allProblems,亦即静态的试题库,那么当所有问题存储到这里,后续使用就可以在这个试题库里查找并输出
接下来对X(学生信息)和D(删除题目信息)进行处理,遇见D则将对应题目设置成不可输出状态
接下来则是组合试卷与答卷,按照输入信息组合
接下来判题并输出
- SourceMonitor测试截图
由图分析可知,平均方法的圈复杂度还是在合理范围内的
但是,对于Tool.processingString()这个方法来说,它的圈复杂度甚至是22,主要在于它得分开各类输入,同时加入判断和循环来区分合法输入
所以在这么高的复杂度和程序不完整不充分的因素下,我没有满分
平均嵌套深度也是很大了,因为每个方法的实现都牵扯到至少两个类方法的调用,而且我在组成试卷和答卷时,用的是在静态“题库”里匹配题目
也就是说要来回不断地匹配索引,这样的话虽然方便,但也加大了嵌套深度和时间复杂度
总结一下:代码的逻辑和设计很清晰,但是对于高速实现、精确处理、优化算法与维护等方面,我的代码还是存在很大的缺陷的
三、3月~4月以来java大作业总结
- 答题判题程序——1
1 import java.util.*; 2 3 public class Main { 4 5 public static void main(String[] args) { 6 Scanner scanner = new Scanner(System.in); 7 .... 8 } 9 } 10 11 class TestPaper{//试卷类,将存储所有题目, 12 .... 13 } 14 15 class Topic{//单独存储一个题目的类 16 .... 17 }
当你第一次写一个诸如此完整的大作业代码时,很容易写成面向过程的代码,例如我,写的时候其实只有两个类,一个是Main类,另一个是卷子类
这样解决问题时虽然快,但是却违反了类与对象的界定。
我第一次提交的满分代码就是两个类。所以后来修改成上述代码的形式,又联系老师和助教请求重新统计分数
所以第一次大作业给我的启示就是:一切皆类,类类分明,什么类干什么事
2.答题判题程序——2(这个属于过度,我们略过这个)
3.答题判题程序——3
让我细数一下踩过的坑
- 对字符串使用 .spilt()切割
1 #N:1 #Q:1+1= #A:2 2 3 #X:20201103 Tom-20201104 Jack-20201105 Www
上述字符串属于标准输入,但是敝人想使用空格作为标识隔断来切割这些字符串
然后再细分
结果在最后测试的时候,有可能会有这样的输入:
#N:1 #Q:1 + 1= #A:2
不得已重新修改存储及处理策略
- 字符串验错程序
首先请诸君看我写的代码:
public static void processingString(LinkedList<String[]> allInformation, String str) { if (!str.matches("^#[A-Z]:.*")) {// 第一次处理非合法输入 System.out.println("wrong format:" + str); } else if (str.startsWith("#T:")) {// 处理基于#T:1 1-5 2-8的问题 String[] data = str.split(" "); String[] temp = new String[data.length + 1]; for(int j=1;j<data.length;j++) { if(!data[j].matches("\\d-.*")) { System.out.println("wrong format:" + str); return; } } temp[0] = "T"; temp[1] = data[0].substring(3).trim(); for (int i = 2; i < temp.length; i++) { temp[i] = data[i - 1]; } Tool.manage100points(temp); allInformation.add(temp); } else if (str.startsWith("#X:")) {// 处理基于#X:20201103 Tom-20201104 Jack-20201104 Www的问题 String input = str; String regex = "^#X:\\d{8}\\s+[\\p{L}\\p{M}\\p{N}\\p{P}\\p{Z}]+(?:-\\d{8}\\s+[\\p{L}\\p{M}\\p{N}\\p{P}\\p{Z}]+)*$"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(input); if (!matcher.matches()) { System.out.println("wrong format:" + str); return; } String[] data = str.split("[-:\\s]+"); data[0] = "X"; allInformation.add(data); } else if (str.startsWith("#D:N-")) {// 处理基于#D:N-2的问题 String[] data = str.split("[-:]+"); data[0] = "D"; allInformation.add(data); } else if (str.startsWith("#N:")) { String aaa = str; String regex = "^#N:\\d+\\s+#Q:.+\\s+#A:.?$"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(aaa); if(!matcher.matches()) { System.out.println("wrong format:" + str); return; } String[] data = str.split("#+"); int a = 1, add = 0; for (int i = 1; i < data.length; i++) { if (!data[i].matches("[A-Z]:.*")) { a = 0; } add++; } // 预先处理#N:1 +1= #A:2非法问题 if (a == 1 && add == 3) { data[0] = "N"; data[1] = data[1].substring(2).trim(); for (int i = 2; i < data.length; i++) { if(data[i].matches("A:")) { data[i]=data[i].substring(2)+" "; continue; } data[i] = data[i].substring(2).trim(); } allInformation.add(data); } else { System.out.println("wrong format:" + str); } } else if (str.startsWith("#S:")) {// 处理基于#S:1 20201103 #A:1-5 #A:2-4的问题 String input = str; String regex = "^#S:\\d+\\s+\\d{8}(?:\\s+#A:\\d*-.*)*$"; Pattern pattern = Pattern.compile(regex); Matcher matcher = pattern.matcher(input); if (!matcher.matches()) { System.out.println("wrong format:" + str); return; } String[] data = str.split("#+"); String[] temp = new String[data.length + 1]; temp[0] = "S"; temp[1] = String.valueOf(data[1].charAt(2)); temp[2] = data[1].substring(4).trim(); for (int i = 3; i < temp.length; i++) { if(data[i-1].matches("A:\\d-\\s+")) { temp[i] = data[i - 1].substring(2)+" "; continue; }else if(data[i-1].matches("A:\\d-")) { temp[i] = data[i - 1].substring(2)+" "; continue; } temp[i] = data[i - 1].substring(2).trim(); } allInformation.add(temp); }else { System.out.println("wrong format:" + str); } }
首先在这一系列验错刚写出来的时候肯定是可读性很高的,但是随着后来越来越深入的去剖析问题,才发现其实以前写的东西远远不够对付测试
所以再一次一次的修改中,这一方法的内容不断扩展,可读性大大降低,且圈复杂度水涨船高
写到后来,每加入新几行代码,只要测试分和编译能过,我就不会再修改原来的,就是害怕改到大动脉然后崩溃
代码一崩溃我就会崩溃。。
后来询问同学,其实以上那些繁琐的正则表达式可以另起一个方法,用数组存储正则表达式,按需查找并输出,同时验证
也可以各个拆分成方法,这样可以降低圈复杂度,也可以利于维护和检查
所以说:利于读懂的代码才更好维护,挤在一堆的代码有问题可能也找不出来
- 基于字符串验错规则之上的数据存储
这张图片显示的是正常存储且验错规则之下,拆分好的数据存储形式(eclipse运行debug调试结果)
那么问题来了,例如:
- 答案为空字符
- 答卷没有答案
我的代码在初步设计时是一定要求有内容的,也就是说对于类似【#S:1 20201103 #A:2-4 #A:1-5】的输入
我要求它学号后必有#A带领的答案,且答案符合【字符 - 字符】格式
但是无论是题目出题要求和实际用户操作情况,我的要求被击碎了
【#S:1 20201103】【#S:1 20201103 #A:2-】等等不仅列为合法输入,同时也要求正常输出
String[] section = temp[j].split("-"); int numToS = Integer.parseInt(section[0]); int answer = Integer.parseInt(section[1]);
这里temp里是【2-4】,numToS指的是序号,answer指的是学生答案
当【#S:1 20201103 #A:2-】出现时,完蛋了,section[1]越界,answer一行语句但不报错
但是当一轮测试下来找到这个点的问题,却也不知道怎么改,因为对【2-】按照【-】为切割字符分出来的数组只有一个元素,那就是【2】
要求的格式输出为:1+1=~~false
意思就是说空答案不仅作为合法字符,然后还要有输出,交空白卷也是有分滴
所以我后来加了个另外的辅助程序:
if(data[i].matches("A:\\d-")) { temp[i] = data[i - 1].substring(2)+"null"; }
这意味着所有【#A:2-】类的字符串在程序运行时,最后会成为:【#A:2-null】
那么再进行其他处理的时候就可以依据null来判断如何判定题目
(内心os:)(但是这也不耽误这种处理方式成为一个好bug)
- 基于#D:N-2的删除题目语句
大作业1和2里一般的【状态】可能有
- 题目判断:对或者错
- 答卷试卷匹配状态:匹配得上或者The test paper number does not exist
- 答卷答案:有答案或者answer is null
- ...
但是大作业3,其中【状态】的【丰富程度】可是真多啊:
- 大作业1和2内的所有
- 学生信息匹配:匹配得上或者not found
- 题号索引:此题号存在或者non-existent question
- 题目状态:正常或者被删除
- ... (代码对我说)
泛泛的讲,这些个状态虽然理解起来容易,但是加到代码里就不是那么回事了
class AnswerPaper // 答卷类,判题结果将在这里产生 private int num = 1; ... private boolean judge = false;//正常判卷结果对错 private boolean key = true;//
public static void main(String[] args) { Scanner scanner = new Scanner(System.in); ... LinkedList<Boolean> judgeToAnswer = new LinkedList<>();//
class Topic {// 单独存储一个题目的类 private String num; ... private boolean condition = true;//
可是加了诸如此的判断仍然有错误输出
4.大作业总结
- 出现的问题:
- 严谨的顶层设计:在做题目之前一定要做好自己对于题目的设计,不然闭着眼睛盲写就会有一堆bug
- 方法的单一职责与注释:500多行的代码写着写着真的容易忘自己写的是什么意思,加注释和方法的单一职责,在调试和修改时的作用真的极大
- 正则表达式的使用:当你发现你写的十多个if-else比不上正则表达式的短短一行,就会发现它的奇妙之处
- 字符串之间的比较:就算老师上课强调过,变量名是一种引用,但是也架不住写的时候直接if(str==arr),所以细节处理之处仍要用心,使用str.euqals()
- 数组越界问题:例如处理空字符,切割出来一个字符子串却要访问两个,在写代码时要处理好数组的问题
2. 学到的知识
- 静态方法的创建与调用
- 用正则表达式处理验错程序
- linkedlist链表和hashmap的使用
- 字符串处理的部分方法与操作
四、归纳与整理
其实在写完全部题目后,我觉得首先的一个感觉就是题目的纵深度提高了不少,尤其是对于细节处理和问题分析
总的来说这也能投影出一部分以后工作了的样子
我觉得有些代码习惯还是不错的:
先仔仔细细读完整个题目,然后按照要求画出图,在图上标注出细节等
因为一个题目的庞大信息量肯定是记不住的,所以想要有一个整体的把握,画个图出来更清晰
遵循Java的命名规范,使用描述性的方法名和适当的命名风格,提高代码的可读性和可维护性。
那么对于之后的代码,我希望首先自己能够有足够的耐心与细心写完题目,同时提高代码设计水平,尽可能多的学习接下来的知识与内容
其次也能够希望老师们能够在题目里描述问题时能够更加详细一点,以来给我们这些清澈 且愚蠢 的大学生更多的学习机会!
顿首祈望老师与同学们提出改进意见!