一、初识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
 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调试结果)

那么问题来了,例如:

  1. 答案为空字符
  2. 答卷没有答案

我的代码在初步设计时是一定要求有内容的,也就是说对于类似【#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里一般的【状态】可能有

  1. 题目判断:对或者错
  2. 答卷试卷匹配状态:匹配得上或者The test paper number does not exist
  3. 答卷答案:有答案或者answer is null
  4. ...

但是大作业3,其中【状态】的【丰富程度】可是真多啊:

  1. 大作业1和2内的所有
  2. 学生信息匹配:匹配得上或者not found
  3. 题号索引:此题号存在或者non-existent question
  4. 题目状态:正常或者被删除
  5. ...   (代码对我说)        

 

泛泛的讲,这些个状态虽然理解起来容易,但是加到代码里就不是那么回事了

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.大作业总结

  1. 出现的问题:
  • 严谨的顶层设计:在做题目之前一定要做好自己对于题目的设计,不然闭着眼睛盲写就会有一堆bug
  • 方法的单一职责与注释:500多行的代码写着写着真的容易忘自己写的是什么意思,加注释和方法的单一职责,在调试和修改时的作用真的极大
  • 正则表达式的使用:当你发现你写的十多个if-else比不上正则表达式的短短一行,就会发现它的奇妙之处
  • 字符串之间的比较:就算老师上课强调过,变量名是一种引用,但是也架不住写的时候直接if(str==arr),所以细节处理之处仍要用心,使用str.euqals()
  • 数组越界问题:例如处理空字符,切割出来一个字符子串却要访问两个,在写代码时要处理好数组的问题

          2. 学到的知识

  • 静态方法的创建与调用
  • 用正则表达式处理验错程序
  • linkedlist链表和hashmap的使用
  • 字符串处理的部分方法与操作

 



 

 四、归纳与整理

其实在写完全部题目后,我觉得首先的一个感觉就是题目的纵深度提高了不少,尤其是对于细节处理和问题分析

总的来说这也能投影出一部分以后工作了的样子

我觉得有些代码习惯还是不错的:

先仔仔细细读完整个题目,然后按照要求画出图,在图上标注出细节等

因为一个题目的庞大信息量肯定是记不住的,所以想要有一个整体的把握,画个图出来更清晰

遵循Java的命名规范,使用描述性的方法名和适当的命名风格,提高代码的可读性和可维护性。

那么对于之后的代码,我希望首先自己能够有足够的耐心与细心写完题目,同时提高代码设计水平,尽可能多的学习接下来的知识与内容

其次也能够希望老师们能够在题目里描述问题时能够更加详细一点,以来给我们这些清澈 且愚蠢  的大学生更多的学习机会!

顿首祈望老师与同学们提出改进意见!

 

posted on 2024-04-16 20:08  AuroraBorealis-wx  阅读(35)  评论(0)    收藏  举报