题目集4~6总结
前言
后三次作业在前三次的基础上更加有针对性,如题目集4针对继承与聚合的初步练习,题目集5主要针对聚合与算法,题目集6针对正则表达式、接口、继承与多态。
题目集4除7-1题外难度中等。7-2题主要是聚合的使用与类中方法的调用,7-3使用继承书写类。
题目集5前三题难度不高,基础题,分别是比较大小、排序和合并数组并排序。7-4难度中上,主要是正则表达式与输入的方式以及字符串替换、输出等。7-5也是聚合,但是与题目集4中7-2聚合方式不同,难度中等。
题目集6包含四个简单题和两个中等题,简单题分别是三个正则表达式训练和一个字符串排序。7-5通过ArrayList以及其sort方法来进行图形面积排序。7-6使用接口来定义图形。
题目集6与题目集5难度非常合适,题目集4中7-1题难度较大,题目难以理解同时工作量较大。总体这三次作业还是比较合适的。
设计与分许
一、比较题目集4(7-2)与题目集5(7-5)两种日期类聚合设计
两题均要求设计程序实现计算日期操作,分别实现三个功能:1、求下n天;2、求前n天;3、求两个日期相差的天数。但两题给出类的聚合方式不同,导致了两题在调用方法的过程中有所不同,虽然在代码结构上有不同,但没有对于整体的代码书写没有很大的差异。
前者:
聚合要求是DateUtil聚合Day类,Day类聚合Month类,Month类聚合Year类。用套娃的方式将各个类链接起来,在保证了日期中年月日的一一对应,确保了在定义一个日期和其数值能够精确得到。
Day、Month与DateUtil类中都有自己数据的set与get方法,也有其下一聚合类的get与set方法,set方法用来设定数值,get方法保证了其能够在自己类本身中调用下一聚合类的方法,因为返回的是下一聚合类的类型数据。各功能的操作都写在了DateUtil里,其中还有对数据的整体校验,保证数据在规定范围内。
类图:

主要难度在于DateUtil中的方法的书写,考察算法。这里展示其中求两日期间隔的方法:
1 //求两个日期之间的天数
2 public int getDaysofDates(DateUtil Date) {
3 if(this.equalTwoDates(Date)) {
4 return 0;
5 }
6 DateUtil min = new DateUtil();
7 DateUtil max = new DateUtil();
8
9 if(this.compareDates(Date)) {
10 max = this;
11 min = Date;
12 }
13 else {
14 max = Date;
15 min = this;
16 }
17 int min_y = min.day.getMonth().getYear().getValue();
18 int min_m = min.day.getMonth().getValue();
19 int min_d = min.day.getValue();
20 int max_y = max.day.getMonth().getYear().getValue();
21 int max_m = max.day.getMonth().getValue();
22 int max_d = max.day.getValue();
23 int dayOf = 0;
24 if(min_y == max_y) {
25 if(min_m == max_m) {
26 dayOf += max_d - min_d;
27 }
28 else {
29 if((min_y % 4 == 0 && min_y % 100 != 0) || min_y % 400 == 0) {
30 min.day.getMon_maxnum()[1] = 29;
31 }
32 dayOf += max_d + (min.day.getMon_maxnum()[min_m - 1] - min_d);
33 for(int i = min_m; i < max_m - 1; i ++) {
34 dayOf += min.day.getMon_maxnum()[i];
35 }
36 }
37 }
38 else {
39 min.day.getMonth().getYear().isLeapYear();
40 max.day.getMonth().getYear().isLeapYear();
41 dayOf += max_d + (min.day.getMon_maxnum()[min_m - 1] - min_d);
42 for(int i = min_m; i < 12; i ++) {
43 dayOf += min.day.getMon_maxnum()[i];
44 }
45 for(int i = max_m - 2; i >= 0; i --) {
46 dayOf += max.day.getMon_maxnum()[i];
47 }
48 if(max_y - min_y > 1) {
49 for(int i = min_y + 1; i < max_y; i ++) {
50 dayOf += 365;
51 if((i % 4 == 0 && i % 100 != 0) || i % 400 == 0) {
52 dayOf += 1;
53 }
54 }
55 }
56 }
57 return dayOf;
58 }
59 }
这里为了不改变原有的数据,对两个日期进行了比较,然后分别将其中保存的值赋给min的年月日与max的年月日中,均为int型数据,方便之后的数值加减。但是这里可以不需要如此复杂,可以直接声明两个新的DateUtil类型,比较之后分别赋值,就不需要这么多的赋值操作。但同时各有利弊,原方法可以减少在get数值时的书写代码量,大幅度减少书写难度,后者减少了赋值操作,检查更加方便。
后者:
此题聚合要求是DateUtil类中分别聚合了Day类、Month类、Year类,这样保证了一个DateUtil就能保存三个数据,然后可以直接调用各个类的方法。
其他题目要求与前者相同,差异在于输出格式不同。
此外,此题中将Day类中的一些方法移动到了DateUtil中,调用方法就与前者有了差别。
类图:

从类图也可以看出,结构不再是前者的条状结构,编程聚合形式的排布,这就是其聚合的不同。
比较:
前者的聚合方式保证了一一对应和数据的分开储存,DateUtil保存Day数据,Day保存自己与Month数据,Month保存自己与Year数据,Year只保存自己,保证了数据的衔接。
后者的聚合方式更加简单,一个DateUtil就能保存Day、Month与Year的数值,调用简单,但是数据间的连接性低。
所以,在数据链接方面前者的聚合更加优秀,但是在程序代码编写方面,后者比前者方便很多,各有优势,也各有劣势。
二、题目集4(7-3)、题目集6(7-5、7-6)三种渐进式图形继承设计的思路与技术应用
题目集4(7-3):
此题要求制作Shape类为父类,自带返回面积的方法getArea(),其直接子类包括Circle类与Rectangle类。再制作两个子类Ball类与Box类分别继承Circle类与Rectangle类。每个子类中都有其属性与属性的get与set方法以及重载的getArea()方法。有体积的子类(Box与Ball)还需再写一个getVolume()方法来得到体积。最后制作ShapeHandle类来进行相应操作。
以下是类图:

所有图形或物体类都是Shape的子类,在书写与逻辑思考上更加简便,用层层继承的方法让图形从类似抽象到二维平面,让物体从二维平面进阶到三维空间。在调用与声明时各自声明子类就能得到需要的图形,复杂程度低。
这里放上ShapeHanle类的代码:
1 //操作 2 class ShapeHandle { 3 Scanner input = new Scanner(System.in); 4 //切割 5 ShapeHandle(){ 6 7 } 8 public String[] Split(String str) { 9 return str.split(" "); 10 } 11 //转换 12 public double[] toDouble(String[] str) { 13 double[] num = new double[str.length]; 14 for(int i = 0; i < str.length; i++) { 15 num[i] = Double.parseDouble(str[i]); 16 } 17 return num; 18 } 19 //判断输入合法性 20 public boolean checkInputValidity(double[] num) { 21 if(num[0] > 4 || num[0] < 1) { 22 return false; 23 } 24 if(num[0] == 1 || num[0] == 3) { 25 if(num.length > 2) { 26 return false; 27 } 28 } 29 else if(num[0] == 2){ 30 if(num.length > 3) { 31 return false; 32 } 33 } 34 else { 35 if(num.length > 4) { 36 return false; 37 } 38 } 39 for(int i = 1; i < num.length; i ++) { 40 if(num[i] < 0) { 41 return false; 42 } 43 } 44 return true; 45 } 46 //操作 47 public void handle() { 48 String str = input.nextLine(); 49 String[] numStr = Split(str); 50 double[] num = toDouble(numStr); 51 if(!checkInputValidity(num)) { 52 System.out.println("Wrong Format"); 53 System.exit(0); 54 } 55 int cho = Integer.parseInt(numStr[0]); 56 if(cho == 1) { 57 Circle circle = new Circle(); 58 circle.setRadius(num[1]); 59 System.out.println("Circle's area:" + String.format("%.2f", circle.getArea())); 60 } 61 if(cho == 2) { 62 Rectangle rectangle = new Rectangle(); 63 rectangle.setWidthLenth(num[1], num[2]); 64 System.out.println("Rectangle's area:" + String.format("%.2f", rectangle.getArea())); 65 } 66 if(cho == 3) { 67 Ball ball = new Ball(); 68 ball.setRadius(num[1]); 69 System.out.println("Ball's surface area:" + String.format("%.2f", ball.getArea())); 70 System.out.println("Ball's volume:" + String.format("%.2f", ball.getVolume())); 71 } 72 if(cho == 4) { 73 Box box = new Box(); 74 box.setWidthLenth(num[1], num[2]); 75 box.setHeight(num[3]); 76 System.out.println("Box's surface area:" + String.format("%.2f", box.getArea())); 77 System.out.println("Box's volume:" + String.format("%.2f", box.getVolume())); 78 } 79 } 80 }
拥有切割、转换、判定与操作四个方法。切割将输入进行切割,变为可识别的字符串组。转换则将字符转化为double型数据,方便判定与输出。判定则对输入的数据与选择进行判定,确认是否能够执行。执行完这些就开始操作,根据选择声明相应的图形,再输出其面积或面积与体积。
切割的方法其实还是多余,这里只是保证输入的内容是否为数字,若不是数字也会输出Wrong Format,但如果题目保证输入是数字的话就不需要此步骤。
题目集6(7-6):
此题要求是制作接口,要求是一个得到面积的接口:GetArea,其中有抽象类getArea(),返回double类型。制作两个类Circle与Rectangle,其中有属性的get与set以及满足接口要求的getArea方法。Main类需要做到输入三个数据,分别是Circle的半径以及Rectangle的两条边长。随后分行输出其两个图形的面积即可。
以下类图及接口与两个子类的代码:

1 interface GetArea { 2 public double getArea(); 3 } 4 5 class Circle implements GetArea { 6 private double radius; 7 Circle(double radius){ 8 this.radius = radius; 9 } 10 Circle(){ 11 12 } 13 public double getRadius() { 14 return this.radius; 15 } 16 public void setRadius(double radius) { 17 this.radius = radius; 18 } 19 @Override 20 public double getArea() { 21 return Math.PI * Math.pow(radius, 2); 22 } 23 } 24 25 class Rectangle implements GetArea { 26 private double width; 27 private double length; 28 Rectangle(double width, double length){ 29 this.length = length; 30 this.width = width; 31 } 32 Rectangle(){ 33 34 } 35 public double getWidth() { 36 return this.width; 37 } 38 public double getLength() { 39 return this.length; 40 } 41 public void setWidth(double width) { 42 this.width = width; 43 } 44 public void setLength(double length) { 45 this.length = length; 46 } 47 @Override 48 public double getArea() { 49 return this.length * this.width; 50 } 51 }
接口在我看来是一个标准化的过程,要求程序员在制作类的时候若要完成功能就要满足接口的要求,让类更加标准化与目的化。
Main中没有像上题一样使用切割的方法得到输入,这里不需要进行选择,则只需要直接输入即可,不过有切割判定当然对程序来说更加安全,保证了得到的输入数据是完全能够识别的,不然输入一个符号、字母,程序将会报错。
题目集6(7-5):
把这题放在这三题中最后说。这题还是继承与多态,主要还是要使用多态。
功能要求是输入一行数字,分别是Circle、Rectangle与Triangle的图形数量,后面是根据其数量输入的相应图形的属性值。输出要先按创建顺序输出其各自的面积,之后输出总面积,再对所有图形进行面积排序,从小到大,然后再输出一次总面积。
这里首先要注意的是排序,如何对不同类的对象进行按某个要素进行排序。这里就是多态的使用了,需要用到数组类ArrayList。
首先输入一行数字,使用切割法进行输入操作,前三个数字存入计数数组里,分别是三个图形的数量。创建ArrayList类对象,其保存的对象是Shape类数据。之后根据三个图形的数量,从切割好的字符串数组中取出相应的属性值。根据数量进行判断要声明的类,声明类后进行计算其面积,将声明的新对象添加进ArrayList数组类里。
之间还要计算其总面积,每声明一个图形,计算后加入总面积变量里,在声明完所有对象后输出。
之后制作排序方法,使用Collection.sort的方法进行排序,制作的compare方法对Shape o1与Shape o2进行了关于getArea的排序,根据其返回值判定其前后顺序。
1 Collections.sort(list, new Comparator<Shape>() { 2 @Override 3 public int compare(Shape s1, Shape s2) { 4 double flag; 5 flag = s1.getArea() - s2.getArea(); 6 return (int)(flag * 1000); 7 } 8 });
这里对返回的值进行了乘以1000并强转int型,是为了保证其小数位后四位都能够进行排序,其实不需要如此,直接排序即可。就算有微小差别也需要排序,并不是小数点后几位相同那就是完全相同。
排序完成后进行输出操作,最后的总面积输出另外按照排序后的数组进行遍历相加即可。
对于类的设计,这里要求使用抽象类Shape来进行继承。Shape为抽象类,其中有抽象方法getArea、抽象方法validate,分别用来返回面积以及检查数据的合法性。还有一个方法toString,用来输出图形面积的两位小数制度。
以下是Shape类以及总类图:
1 abstract class Shape { 2 public abstract double getArea(); 3 public abstract boolean validate(); 4 public String toString() { 5 return String.format("%.2f", this.getArea()); 6 } 7 }

总体复杂度不高,主要还是使用了多态进行更加简便的书写与操作,逻辑简单清晰。
三、对三次题目集中使用到的正则表达式进行分析总结
正则表达式是一个很方便的判定工具,它能让我们在输入数据判定方面得到很多方便,不再需要全部使用数字代码来进行字符的判断。其也方便了切割与转换等多个方面的操作。
题目集6(7-1)QQ号校验需要使用正则表达式对输入的号码进行判断,首先是第一位不能为0,所以首先要定义第一位为[1-9],限定第一位不为0。之后限制后面都是数字,加上\\d。再限制位数,由于第一位已经定义了,所以在\\d后更上{1,14},限制了四到十四个任意数字,保证了整个QQ号位数为五到十五。
题目集6(7-3)验证码校验和QQ号一样需要限制位数,但是不同的是它不需要限制第一位为0以及可以出现大小写字母。可以为数字、大写字母以及小写字母,所以可以用或来分割,写成[\\d|a-z|A-Z],\\d是数字,a-z是小写字母,A-Z是大写字母。之后限制位数,加上{4}即可,限制总共4为。
题目集6(7-4)学号校验题目给学号进行了限制,不像QQ号的随意数字。首先前四位的学院及级别,2020直接写,之后是班级,由于班级有限制,所以这里选择了将所有班级列出来,用或符号分割,“11|12|13|14|15|16|17|61|71|72|73|81|82”,最后是号数,一位数的学号后两位是要添0的,所以要和两位数分开写,一位数写成“0[1-9]”,两位数的由于一个班最多四十号,所以还要和四十号分开,写成“[1-3]\\d|40”,再和一位数的合并。
踩坑心得
1、题目集4日期类聚合的mon_maxnum在进行闰年判定的时候若为闰年,须在二月进行加一天操作,但是,在循环遍历的过程中,如果不是声明新的对象操作,那遍历到多个闰年时2月就进行了多次加一天操纵,所以不要用加1操作,而是直接赋值为29,若不是闰年则赋值为28,保证了日期不会有差异。
2、题目集4日期类聚合在创建新对象时一定要将每个对象里的下一个类创建出来,如创建DateUtil时一定要创建Day,创建Day时要创建Month,创建Month时要创建Year,不然操作的时候会调用不出对象,报错空对象。
1 //带参构造方法 2 DateUtil(int d, int m, int y){ 3 day = new Day(); 4 day.setValue(d); 5 day.getMonth().setValue(m); 6 day.getMonth().getYear().setValue(y); 7 if(day.getMonth().getYear().isLeapYear()) { 8 day.getMon_maxnum()[1] = 29; 9 } 10 else 11 day.getMon_maxnum()[1] = 28; 12 }
3、两个日期操作通病,在整体的数据校验时一定要从大的网小的校验,就是先校验年,再校验月,最后校验日期。这么做的目的是先保证年份不超过限制,再保证月份及日期的合法性。
1 //校验数据合法性 2 public boolean checkInputValidity() { 3 if(this.year.validate() && this.month.validate() && this.validate()) { 4 return true; 5 } 6 else 7 return false; 8 }
4、两个日期操作的求前n天与求后n天操作,都要从日期开始变化,直到超过合法范围再更变月份,直到月份不合法再更变年份,保证了在一个月内的日期最大值是准确的,且在年份变化后马上判定是否为润年。
1 //求下n天 2 public DateUtil getNextNDays(int n) { 3 DateUtil date = new DateUtil(); 4 date = this; 5 for(int i = 0; i < n ; i ++) { 6 date.day.dayIncrement(); 7 if(!date.validate()) { 8 date.month.monthIncrement(); 9 if(!date.month.validate()) { 10 date.year.yearIncrement(); 11 if(date.year.isLeapYear()) { 12 date.mon_maxnum[1] = 29; 13 } 14 else 15 date.mon_maxnum[1] = 28; 16 date.month.resetMin(); 17 } 18 date.setDayMin(); 19 } 20 } 21 return date; 22 } 23 //求前n天 24 public DateUtil getPreviousNDays(int n) { 25 DateUtil date = new DateUtil(); 26 date = this; 27 for(int i = 0; i < n ; i ++) { 28 date.day.dayReductin(); 29 if(!date.validate()) { 30 date.month.monthReduction(); 31 if(!date.month.validate()) { 32 date.year.yearReduction(); 33 if(date.year.isLeapYear()) { 34 date.mon_maxnum[1] = 29; 35 } 36 else 37 date.mon_maxnum[1] = 28; 38 date.month.resetMax(); 39 } 40 date.setDayMax();; 41 } 42 } 43 return date; 44 }
4、题目集6图形类多态,由于切割后的数据需要取出,但数据量又比较复杂,所以要仔细计算其个数,如一个圆一个矩形一个三角形,则圆取第一个数字,矩形取圆的数量减一开始的数据,并且一次取两个,三角形取圆的数量减一加上矩形的数量乘二的位置,并且一次取三个。
1 //Circle类声明 2 double sumOfCircle = 0; 3 for(int i = 0; i < count[0]; i ++) { 4 double radius = Double.valueOf(num[i + 3]); 5 list.add(new Circle(radius)); 6 if(!list.get(i).validate()) { 7 System.out.println("Wrong Format"); 8 System.exit(0); 9 } 10 sumOfCircle += list.get(i).getArea(); 11 } 12 //Rectangle类声明 13 double sumOfRectangle = 0; 14 int j = count[0] + 3; 15 for(int i = count[0]; i < count[0] + count[1]; i ++, j += 2) { 16 double width = Double.valueOf(num[j]); 17 double length = Double.valueOf(num[j + 1]); 18 list.add(new Rectangle(width, length)); 19 if(!list.get(i).validate()) { 20 System.out.println("Wrong Format"); 21 System.exit(0); 22 } 23 sumOfRectangle += list.get(i).getArea(); 24 } 25 //Triangle类声明 26 double sumOfTriangle = 0; 27 j = count[0] + count[1] * 2 + 3; 28 for(int i = count[0] + count[1]; i < count[0] + count[1] + count[2]; i ++, j += 3) { 29 double side1 = Double.valueOf(num[j]); 30 double side2 = Double.valueOf(num[j + 1]); 31 double side3 = Double.valueOf(num[j + 2]); 32 list.add(new Triangle(side1, side2, side3)); 33 if(!list.get(i).validate()) { 34 System.out.println("Wrong Format"); 35 System.exit(0); 36 } 37 sumOfTriangle += list.get(i).getArea(); 38 }
总结
这三次题目集大多是对类的操作进行训练,主要还是多态、聚合与继承。继承能够让子类的书写更加简便,在后期需要修改程序的话能减少书写量。聚合是一个加强类与类的联系的方式,并且让类能调用另一个类,让结构更加紧凑。多态是一个重要的书写方式,其形态各异,很多地方都能够使用多态减轻代码书写量以及让整体逻辑更加简便,这也是java的强大之一。
浙公网安备 33010602011771号