面向对象程序设计第二次博客
1、前言
- 题目集四考察的是类之间的关系和正则表达式的应用,比较类之间的继承和聚合关系,还有比较用聚合关系写的类和之前题目集用组合关系写的类的不同,题目有三道题,第一题较难,另外两题适中。
- 题目集五考察的是使用不同种类的聚合对程序的影响,以及正则表达式的简单应用,还复习了三大排序算法,考察查找统计java程序钟关键词出现的个数,需要用到List、Set或Map中一种或多种,这里考察聚合关系的题与上一个题目集考察聚合关系的题是一样的,只是类之间的关系变了,需要我们去比较哪一种的设计更加方便,题目集五有五道题,前三道考察基本知识,第五题是类的聚合的修改,第四题是考察查找关键字,整体难度适中。
- 题目集六主要考察的是类的继承与多态,还考察了接口和抽象类的应用,以及正则表达式的简单应用,从这里开始接触到了多态的应用,也开始对list接口有了了解,题目集六有六道题,1,3,4题考察的是正则表达式的简单应用,第二题考察了字符串内字符的排序,我用了list以及相应的方法更简单的排序,第六题考察的是接口和多态,第五题主要考察图形的继承和多态的使用,同样也应用了list接口的相应知识,题目量不大,整体难度适中。
2、设计与分析
题目集4(7-2)、题目集5(7-5)两种日期类聚合设计的优劣比较
- 首先在写题目集4(7-2)的时候就发现7-2的设计极其复杂,调用year类之中的方法还需要从date-day-month最后才能到year方法,非常麻烦,调用起来也是一连串很长的反复套用,7-2的PowerDesigner类图和SourceMonitor生成报表内容如下:
- PowerDesigner类图
![]()
从类图中就可以看出来一层一层调用的是多么复杂。 - SourceMonitor生成报表内容
![]()
- 圈复杂度达到了34,非常复杂,而且代码的复杂度也是一眼看去乱成一团,运用了大量的if-else语句来判断,在写程序的时候对类进行反复调用看着就很乱,如下图:
![]()
- 上图的方法是判断输入的两个日期的大小,这种的调用方法就很离谱,为什么调用一个year类之间的方法得按照顺序一遍遍的调用,而且很容易就看错了顺序导致程序出错,然后在题目集5(7-5)的时候就写出来了更好的方法,觉得题目集五的7-5比上一题的调用方法好太多了,至少简洁又清晰,7-5的PowerDesigner类图和SourceMonitor生成报表内容如下:
- PowerDesigner类图
![]()
这个类图就好很多了,类DateUtil调用三个日期类,简单清晰 - SourceMonitor生成报表内容
![]()
- 由图可知圈复杂度并没有发生变化,还是34,原因是我在写这两道题的时候用的是同一种方法,只不过根据类图的变化把类之间的关系给变了一下,但是代码的清晰度第二个比第一个好很多,上面放了一个第一题的算两个日期大小的方法,第二题相同的方法如下:
![]()
- 再比较一下上面的非常复杂的调用,这一个方法看起来要简洁很多,我也认为题目集5(7-5)比题目集4(7-2)要好很多。
- 解题思路就是按照类图给的方法来写,只有在类之间的方法自己写的比较多。
- 判断两个日期的先后就是先判断年,在判断月最后判断日,代码如下:
public boolean compareDates(DateUtil date) {
if(year.getValue() < date.getYear().getValue()) {
return false;
}
else if(year.getValue() == date.getYear().getValue()) {
if(month.getValue() < date.getMonth().getValue()) {
return false;
}
else if(month.getValue() == date.getMonth().getValue()) {
if(day.getValue() < date.getDay().getValue()) {
return false;
}
}
}
return true;
}
- 然后判断两个日期是否相等就很简单的一个if-else判断,年月日一起比较。
- 求下n天在题目集四的7-2里面用的是一个循环直接调用day类里面的下一天的方法,循环n次,如下:
public DateUtil getNextNDays(int n) {
DateUtil dateutil = new DateUtil(day.getMonth().getYear().getValue(),day.getMonth().getValue(),day.getValue());
for(int i = 0;i < n;i ++) {
dateutil.getDay().dayIncrement();
}
return dateutil;
}
求前n天也是一样的,用一个循环来反复调用下一天的方法
public DateUtil getPreviousNDays(int n) {
DateUtil dateutil = new DateUtil(day.getMonth().getYear().getValue(),day.getMonth().getValue(),day.getValue());
for(int i = 0;i < n;i ++) {
dateutil.getDay().dayReduction();
}
return dateutil;
}
- 但是在题目集5(7-5)中所用的方法不同,因为这道题的类之间的关系不一样,没办法在day里面调用month和year的方法,所以只能使用另一种方法来实现下n天,先一天天的加day的天数,然后直到最后加到n天,期间要判断月和年是否需要增加:
public DateUtil getNextDays(int n) {
DateUtil dateutil = new DateUtil(year.getValue(), month.getValue(), day.getValue());
int count = n/146097;
dateutil.getYear().setValue(dateutil.getYear().getValue() + 400*count);
n = n%146097;
for(int i = 0;i < n;i ++) {
dateutil.getDay().dayIncrement();
if(dateutil.getYear().isLeapYear()) {
mon_maxnum[1] = 29;
}
else if(!dateutil.getYear().isLeapYear()) {
mon_maxnum[1] = 28;
}
if(dateutil.getDay().getValue() > mon_maxnum[dateutil.getMonth().getValue() - 1]) {
dateutil.getMonth().monthIncrement();
dateutil.getDay().setValue(1);
}
if(dateutil.getMonth().getValue() > 12) {
dateutil.getYear().yearIncrement();
dateutil.getMonth().resetMin();
}
}
return dateutil;
}
求前n天也是一样的:
public DateUtil getPreviousNDays(int n) {
DateUtil dateutil = new DateUtil(year.getValue(), month.getValue(), day.getValue());
int count = n/146097;
dateutil.getYear().setValue(dateutil.getYear().getValue() - 400*count);
n = n%146097;
for(int i = 0;i < n;i ++) {
dateutil.getDay().dayReduction();
if(dateutil.getYear().isLeapYear()) {
mon_maxnum[1] = 29;
}
else if(!dateutil.getYear().isLeapYear()) {
mon_maxnum[1] = 28;
}
if(dateutil.getDay().getValue() < 1) {
dateutil.getMonth().monthReduction();
if(dateutil.getMonth().getValue() < 1) {
dateutil.getYear().yearReduction();
dateutil.getMonth().resetMax();
}
dateutil.getDay().setValue(mon_maxnum[dateutil.getMonth().getValue() - 1]);
}
}
return dateutil;
}
- 然后最难也是最麻烦的是求两个日期之间的天数,我采取的方法是先调用比较大小的方法,然后计算之间的年份差,再计算月份差,最后再实现天数的计算,由于两道题所用方法一样,这里只放上第二题的相应方法的源码:
public int getDaysofDates(DateUtil date) {
int text = 0;
if(equalTwoDates(date)) {
text = 0;
}
else if(compareDates(date)) { //day>date
if(year.getValue() == date.getYear().getValue()
&& month.getValue() == date.getMonth().getValue()) {
text = day.getValue() - date.getDay().getValue();
}
else if(year.getValue() == date.getYear().getValue()) {
if(year.isLeapYear()) {
mon_maxnum[1] = 29;
}
text += mon_maxnum[date.getMonth().getValue() - 1] - date.getDay().getValue();
for(int i = date.getMonth().getValue() + 1;i < month.getValue();i ++) {
text += mon_maxnum[i-1];
}
text += day.getValue();
mon_maxnum[1] = 28;
}
else {
int tt = 0;
int t = date.getYear().getValue();
for(int i = date.getYear().getValue() + 1;i < year.getValue();i ++) {
date.getYear().setValue(i);
if(date.getYear().isLeapYear()) {
text += 366;
}
else {
text += 365;
}
}
date.getYear().setValue(t);
if(date.getYear().isLeapYear()) {
mon_maxnum[1] = 29;
}
for(int i = 1;i < date.getMonth().getValue();i ++) {
tt += mon_maxnum[i-1];
}
mon_maxnum[1] = 28;
tt += date.getDay().getValue();
if(date.getYear().isLeapYear()) {
text += 366 - tt;
}
else {
text += 365 - tt;
}
if(year.isLeapYear()) {
mon_maxnum[1] = 29;
}
for(int i = 1;i < month.getValue();i ++) {
text += mon_maxnum[i-1];
}
text += day.getValue();
mon_maxnum[1] = 28;
}
}
else { //date>day
if(year.getValue() == date.getYear().getValue()
&& month.getValue() == date.getMonth().getValue()) {
text = day.getValue() - date.getDay().getValue();
}
else if(year.getValue() == date.getYear().getValue()) {
if(date.getYear().isLeapYear()) {
mon_maxnum[1] = 29;
}
text += mon_maxnum[month.getValue() - 1] - day.getValue();
for(int i = month.getValue() + 1;i < date.getMonth().getValue();i ++) {
text += mon_maxnum[i-1];
}
text += date.getDay().getValue();
mon_maxnum[1] = 28;
}
else {
int tt = 0;
int t = year.getValue();
for(int i = year.getValue() + 1;i < date.getYear().getValue();i ++) {
year.setValue(i);
if(year.isLeapYear()) {
text += 366;
}
else {
text += 365;
}
}
year.setValue(t);
if(year.isLeapYear()) {
mon_maxnum[1] = 29;
}
for(int i = 1;i < month.getValue();i ++) {
tt += mon_maxnum[i-1];
}
mon_maxnum[1] = 28;
tt += day.getValue();
if(year.isLeapYear()) {
text += 366 - tt;
}
else {
text += 365 - tt;
}
if(date.getYear().isLeapYear()) {
mon_maxnum[1] = 29;
}
for(int i = 1;i < date.getMonth().getValue();i ++) {
text += mon_maxnum[i-1];
}
text += date.getDay().getValue();
mon_maxnum[1] = 28;
}
}
return (int)text;
}
题目集4(7-3)、题目集6(7-5、7-6)三种渐进式图形继承设计的思路与技术运用(封装、继承、多态、接口等)
- 题目集4(7-3)
- 这道题主要还是考察的类的继承,父类和子类之间的关系,设计几个图形来计算相应的面积,PowerDesigner类图和SourceMonitor生成报表内容如下:
- PowerDesigner类图
![]()
- SourceMonitor生成报表内容
![]()
- 由图可知圈复杂度为13,在主方法中设置了一个switch的用法来降低圈复杂度,类Shape,无属性,里面的getArea()方法可以看成是抽象方法,然后由类图可以看出类Circle,继承自Shape,类Rectangle,继承自Shape,类Ball,继承自Circle,类Box,继承自Rectangle。
- 题目有个要求是每个类均有构造方法,且构造方法内必须输出如下内容:Constructing 类名,然后思路就是在每一个类声名的时候加上一句输出:
- PowerDesigner类图
public Shape() {
super();
System.out.println("Constructing Shape");
// TODO Auto-generated constructor stub
}
public Circle() {
super();
System.out.println("Constructing Circle");
// TODO Auto-generated constructor stub
}
- 题目集6(7-5)
- 该题考查的是对抽象类的应用,以及对继承的拓展,相比较于题目集4(7-3)对类的继承概念的使用加深,设置一个shape类的抽象类和几个继承自抽象类的图形类,来计算面积。
PowerDesigner类图和SourceMonitor生成报表内容如下:-
PowerDesigner类图
![]()
-
由图可知Circle,Rectangle,Triangle三个类均继承自Shape抽象类,而四个类都是依赖自Main类,在Main类中这四个类都得到了声明。
-
SourceMonitor生成报表内容
![]()
-
由图可知圈复杂度为14,在主方法中使用了判断语句判断输入的数据是否正确,还运用了多种for循环来实现功能,导致圈复杂度到了14。
-
题目要求设置一个toString类来返回保留两位小数的最后面积,一开始写的时候并不知道这个方法的具体用处,只是在抽象类中设置了之后需要写上去,然后写到后面发现原来是调用算图形面积的方法然后输出保留两位小数之后的数据:
-
@Override
public String toString() {
// TODO Auto-generated method stub
return String.format("%.2f", getArea());
}
- 除了Main类之外的所有类都是根据题目意思来写的,三角形知道三边需算出面积的公式之前忘记了,上网百度之后才发现原来有直接利用三边求面积的公式:
public double getArea() {
double c = (side1+side2+side3)/2;
double s = Math.sqrt(c*(c-side1)*(c-side2)*(c-side3));
return s;
}
- 题目意思中还给出了一定要求各个图形对象均存储在 ArrayList
类型的列表中,根据图形的面积大小进行升序排序,要求必须对 list 中的图形对象在 list 中进行排序,而不是对求得的面积进行排序,排序后再次求出各图形的面积并输出。 - 这就应用到了List的具体方法,也是对List接口类的一个了解,刚开始并不知道怎么做,然后想到了要用对象数组存入List之间,用这个办法将输入的所有对象数组的元素全部导入到List中,之后再对单个对象进行操作:
ArrayList<Shape> shape = new ArrayList<>();
Circle[] cir = new Circle[a + 1];
Rectangle[] rec = new Rectangle[b + 1];
Triangle[] tri = new Triangle[c + 1];
for(int i = 0;i < a;i ++) {
double banjing = input.nextDouble();
cir[i] = new Circle(banjing);
shape.add(cir[i]);
}
for(int i = 0;i < b;i ++) {
double kuan = input.nextDouble();
double chang = input.nextDouble();
rec[i] = new Rectangle(kuan,chang);
shape.add(rec[i]);
}
for(int i = 0;i < c;i ++) {
double aa = input.nextDouble();
double bb = input.nextDouble();
double cc = input.nextDouble();
tri[i] = new Triangle(aa,bb,cc);
shape.add(tri[i]);
}
- 判断数据是否正确:
for(int i = 0;i < shape.size();i ++) {
if(!shape.get(i).validate()) {
System.out.println("Wrong Format");
System.exit(0);
}
}
- 然后输入完了并且判断正确之后就需要先进行输出,然后在进行计算总面积,之后进行排序后输出,排序的时候使用了冒泡排序的算法,直接在List中进行排序,其实应该有更简单的排序,可是还是选择了先比较大小再排序,在List的相应的方法中找到了交换顺序的方法Collections.swap,然后就可以使用冒泡进行一个对象数组的排序:
System.out.println("Original area:");
double total = 0;
for(int i = 0;i < shape.size();i ++) {
System.out.print(shape.get(i).toString() + " ");
total += shape.get(i).getArea();
}
System.out.print('\n');
System.out.println("Sum of area:" + String.format("%.2f",total));
System.out.println("Sorted area:");
double t;
for(int i = 0;i < shape.size();i ++) {
for(int j = 0;j < shape.size() - i - 1;j ++) {
if(shape.get(j).getArea() > shape.get(j+1).getArea()) {
Collections.swap(shape, j, j+1);
}
}
}
for(int i = 0;i < shape.size();i ++) {
System.out.print(shape.get(i).toString() + " ");
}
System.out.print('\n');
System.out.println("Sum of area:" + String.format("%.2f",total));
- 题目集6(7-6)
- 这道题就是让我们来熟悉一下抽象类接口的使用,然后算圆和矩形的面积,题目的类图都已经给出来了,很简单,按照类图写就可以了,对接口的继承以及实现多态性还需要好好掌握
PowerDesigner类图和SourceMonitor生成报表内容如下:- PowerDesigner类图
![]()
- SourceMonitor生成报表内容
![]()
- 由图可知类Circle和类Rectangle均是继承自接口类GetArea,而该接口类中只有一行代码,设置一个抽象方法,下面所有继承自接口类的子类中都要覆写该方法:
public double getArea(); - 圈复杂度为4,这应该是我写过的圈复杂度最低的代码了,首先代码简单,只是考察了很基础的对多态的使用,而在主函数中我只是用了一个判断语句来判断输入数据是否正确,很简洁,类之中只有计算面积的方法,主方法中也只有对数据的一些操作,主方法如下:
- PowerDesigner类图
public static void main(String[] args) {
// TODO Auto-generated method stub
Scanner input=new Scanner(System.in);
double cir = input.nextDouble();
double kuan = input.nextDouble();
double chang = input.nextDouble();
if(cir <= 0 || kuan <= 0 || chang <= 0) {
System.out.println("Wrong Format");
System.exit(0);
}
Circle circle = new Circle(cir);
Rectangle rectangle = new Rectangle(kuan,chang);
System.out.println(String.format("%.2f", circle.getArea()));
System.out.println(String.format("%.2f", rectangle.getArea()));
}
对三次题目集中用到的正则表达式技术的分析总结
-
又经过了很久的学习正则表达式,越学就越觉得正则表达式是一个非常强大的字符串操作方法,基本上特殊的一组字符串只要用正则表达式匹配就可以很简单的分割各个数据,同时也可以非常轻易地对这些分割的字符串进行操作了。
-
题目集四中的7-1运用到了正则表达式,这道题我觉得是一道非常复杂的题目,对数据的处理也非常的大,水文数据太复杂,调试了很久很久程序才终于能进行运行,然后又是很久很久的调试正确答案,做到最后也没能得满分。
PowerDesigner类图和SourceMonitor生成报表内容如下:-
PowerDesigner类图
![]()
-
这类图是题目指导书上面给的,给出了一个思路,很麻烦的一道题,还有类之间的关系调用图:
![]()
-
SourceMonitor生成报表内容
![]()
-
圈复杂度达到了18,这还是大部分判断都用了正则表达式的结果。
-
解题思路大部分是按照题目给出的类图来写的,题目只给出了大概的整体思路。
-
我的思路是先判断输入的数据是否由‘|’分割的五段数据,然后在分割出五段之后消除多余的空格,每一段数据的两端在输入的时候是可以带空格的,我们需要消除多余的空格,然后再一段段数据存入字符串数组中,然后每一个数据运用正则表达式判断。
判断五段分割:
-
public String[] getDate() {
String[] str = new String[6];
Pattern p = Pattern.compile("(.+)\\|(.+)\\|(.+)\\|(.+)\\/(.+)\\|(.+)");
Matcher m = p.matcher(date);
m.matches();
for (int i = 0; i < 6; i++) {
str[i] = m.group(i + 1);
str[i] = str[i].trim();
}
return str;
}
判断时间是否正确之中还需要判断是不是闰年的天数,月份是否准确,时间是否准确之类的题目要求的是小时数必须是2的倍数,分钟必须是00,而且日期和时间之间有一个空格:
public boolean vaildateMeasureDateTime(String measureDateTime) {
Pattern p = Pattern.compile("(\\d+)(\\/)(\\d+)(\\/)(\\d+)(\\ )(\\d+)(\\:)(00)");
Matcher m = p.matcher(measureDateTime);
int[] mon = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
if (m.find()) {
String s1 = m.group(1);
String s2 = m.group(3);
String s3 = m.group(5);
String s4 = m.group(7);
int tt = Integer.parseInt(s1);
int mm = Integer.parseInt(s2);
int dd = Integer.parseInt(s3);
if (s1.charAt(0) == '0') {
return false;
} else {
if (tt < 1 || tt > 9999) {
return false;
}
}
if (s2.charAt(0) == '0') {
return false;
} else {
if (mm < 1 || mm > 12) {
return false;
}
}
mon[1] = 28;
if ((tt % 4 == 0 && tt % 100 != 0) || tt % 400 == 0) {
mon[1] = 29;
}
if (s3.charAt(0) == '0') {
return false;
} else {
if (dd < 1 || dd > mon[mm - 1]) {
return false;
}
}
int text = Integer.parseInt(s4);
if (text % 2 != 0) {
return false;
}
return true;
}
return false;
}
然后判断那些实际水位和目标水位和流量,也是按照题目意思来设计正则表达式判断,取值范围为[1,1000),小数点后保留 1-3 位小数或无小数(此时不需要写小数点),而且中间除了小数点是字符之外其他的全是数字:
public boolean validateWaterLevel(String waterLevel) {
Pattern p = Pattern.compile("[1-9]\\d{0,3}(\\.\\d{1,3})?");
Matcher m = p.matcher(waterLevel);
if (m.matches()) {
return true;
}
return false;
}
再进行判断的是目标开度、实际开度,取值范围为[1,10),必须保留 2 位小数,两个开度之间用“/”分隔,而且还需要比较两个开度之间的大小,当实际开度的值大于目标开度时,程序给出如下警告:
Row:行号 GateOpening Warning
这就多了一个判断大小的语句:
public boolean validateGateOpening(String getOpening) {
Pattern p = Pattern.compile("((\\d+)(\\.)(\\d{2}))(\\b)");
Matcher m = p.matcher(getOpening);
if (m.find()) {
int a = Integer.parseInt(m.group(2));
if (a < 1 || a >= 10) {
return false;
}
return true;
}
return false;
}
然后直到所有的单项数据都判定完成之后,就是把单个的一些数据一个个的传入相应的实体类中,还用到了日期方法,再设置一个匹配所有的正则表达式来匹配整体的情况:
public HydrologicalInfo toHydrologicalInfo() {
Pattern p = Pattern.compile("(" + "((\\d+)(\\/)(\\d+)(\\/)(\\d+)(\\ )(\\d+)(\\:)(\\d+))"
+ "(\\|)((\\d+)(\\.)?(\\d+)?)" + "(\\|)((\\d+)((\\.)?)((\\d+)?))" + "(\\|)((\\d+)(\\.)?(\\d+)?)"
+ "(\\/)((\\d+)(\\.)?(\\d+)?)" + "(\\|)((\\d+)(\\.)?(.+)))");
Matcher m = p.matcher(date);
if (m.find()) {
LocalDateTime date1 = LocalDateTime.of(2021, 1, 1, 1, 1);
int year = Integer.parseInt(m.group(3));
int month = Integer.parseInt(m.group(5));
int day = Integer.parseInt(m.group(7));
int hour = Integer.parseInt(m.group(9));
int min = Integer.parseInt(m.group(11));
date1 = LocalDateTime.of(year, month, day, hour, min);
double objectWaterLevel = Double.parseDouble(m.group(13)); // 目标水位
double actualWaterLevel = Double.parseDouble(m.group(18)); // 实际水位
double objectGateOpening = Double.parseDouble(m.group(25)); // 目标开度
double actralGateOpening = Double.parseDouble(m.group(30)); // 实际开度
double waterFlow = Double.parseDouble(m.group(35)); // 流速
HydrologicalInfo hy = new HydrologicalInfo(date1, objectWaterLevel, actualWaterLevel, objectGateOpening,
actralGateOpening, waterFlow);
return hy;
}
return null;
}
判断完成后在vaildateDate里面进行数据的校验并且输出相应的需要输出的语句:
public boolean vaildateDate() {
int cnt = 0;
for (int i = 0; i < date.length(); i++) {
if (date.charAt(i) == '|') {
cnt++;
}
}
if (cnt != 4) {
System.out.println("Wrong Format");
System.out.println(this.date);
return false;
}
String[] str = new String[6];
str = getDate();
if (vaildateMeasureDateTime(str[0]) && validateWaterLevel(str[1]) && validateWaterLevel(str[2])
&& validateGateOpening(str[3]) && validateGateOpening(str[4]) && validateWaterLevel(str[5])) {
return true;
} else {
if (!vaildateMeasureDateTime(str[0])) {
System.out.println("Row:" + this.row + ",Column:" + 1 + "Wrong Format");
}
if (!validateWaterLevel(str[1])) {
System.out.println("Row:" + this.row + ",Column:" + 2 + "Wrong Format");
}
if (!validateWaterLevel(str[2])) {
System.out.println("Row:" + this.row + ",Column:" + 3 + "Wrong Format");
}
if (!validateGateOpening(str[3])) {
System.out.println("Row:" + this.row + ",Column:" + 4 + "Wrong Format");
}
if (!validateGateOpening(str[4])) {
System.out.println("Row:" + this.row + ",Column:" + 5 + "Wrong Format");
}
if (!validateWaterLevel(str[5])) {
System.out.println("Row:" + this.row + ",Column:" + 6 + "Wrong Format");
}
System.out.println("Data:" + this.date);
return false;
}
}
这道题就这么写完了,主方法中有一个判断输入数据是否连续为空的判断语句,也是用正则表达式判断的:

可是最后还是没有满分,可能是我之中的一些数据判断的并不严谨。
- 题目集五的7-4中用到了正则表达式,也是一道很复杂的题目,用了List的相应方法,还有map和set的方法,三个接口全用上了,还用了Object数组,方便排序后输出,53个关键字直接就设置成了一个正则表达式,然后把多余的关键字给设置成空,注释中出现的关键字不用统计,字符串中出现的关键字不用统计,【】中的关键字不用统计,.之后的关键字不用统计,四则运算的关键字也不用统计:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
List<String> list = new ArrayList<String>();
String s = new String();
String key = "abstract|assert|boolean|break|byte|case|catch|"
+ "char|class|const|continue|default|do|double|else|enum"
+ "|extends|false|final|finally|float|for|goto|if|implements|"
+ "import|instanceof|int|interface|long|native|new|null|"
+ "package|private|protected|public|return|short|static|"
+ "strictfp|super|switch|synchronized|this|throw|throws|true|" + "transient|try|void|volatile|while";
while ((s = input.nextLine()) != null) {
if (s.equals("exit")) {
break;
}
list.add(s);
list.add("\n");
}
String str = "";
for (int i = 0; i < list.size(); i++) {
str = str + list.get(i);
}
if (str.matches("^\\s*|\\s*$") || str == null) {
System.out.println("Wrong Format");
System.exit(0);
}
Pattern p = Pattern.compile("\\b(" + key + ")\\b");
str = str.replaceAll("\\/\\/.+", " "); //去掉注释
str = str.replaceAll("/\\*(.*\\n)*?.*?\\*/", " ");
str = str.replaceAll("\\\"(.*\\n)*?.*?\\\"", " ");
str = str.replaceAll("\\[(.*\\n)*?.*?\\]", " ");
str=str.replace("=","a");
Map<String, Integer> map = new HashMap<String, Integer>();
Matcher m = p.matcher(str);
while (m.find()) {
map.put(m.group(), 0);
}
int count = 0;
Matcher m1 = p.matcher(str);
while (m1.find()) {
count = map.get(m1.group()); //返回指定键所映射的值
map.put(m1.group(), count + 1);
}
// 输出:
Set<String> set = map.keySet();
Object[] arr=set.toArray();
Arrays.sort(arr); //排序
for(Object k:arr){
System.out.println(map.get(k)+"\t"+k);
}
}
}
- 后面的题目集六就是很简单的正则表达式的校验
- 7-1的QQ号校验:
Pattern p = Pattern.compile("^[1-9][0-9]{4,14}$"); - 7-3的验证码校验:
Pattern p = Pattern.compile("^[1-9|a-z|A-Z]{4}$"); - 7-4的学号校验:
Pattern p = Pattern.compile("^((2020)(([1][1-7])|(61)|([7][1-3])|([8][1-2]))([1-3][0-9]|(40)|[0][1-9]))$"); - 正则表达式的训练我觉得还是很有必要的,让我们多熟悉熟悉,之后的做题也能更好更快的做完,然后在熟悉的过程中还能多学学语法。
题目集5(7-4)中Java集合框架应用的分析总结
- 这个作业应该也是第一次自己去接触三个接口(List、Set、Map),List了解的比较多,List接口是Collection接口的子接口,List有一个重要的实现类--ArrayList类,List中的元素是有序排列的而且可重复,所以被称为是序列。
List可以精确的控制每个元素的插入位置,或删除某个位置元素,它的实现类ArrayList底层是由数组实现的。
主要还是用list来解题,然后就自己在CSDN上学习list的相关语法:
![]()
只用list是不够的,在这道题中我还使用了map和set的相关方法,以及一个Object数组的使用,最后还上网查找了一个增强的for循环来做题目的最后输出:
Map<String, Integer> map = new HashMap<String, Integer>();
Matcher m = p.matcher(str);
while (m.find()) {
map.put(m.group(), 0);
}
int count = 0;
Matcher m1 = p.matcher(str);
while (m1.find()) {
count = map.get(m1.group()); //返回指定键所映射的值
map.put(m1.group(), count + 1);
}
// 输出:
Set<String> set = map.keySet();
Object[] arr=set.toArray();
Arrays.sort(arr); //排序
for(Object k:arr){
System.out.println(map.get(k)+"\t"+k);
}
然后为了正确的输出,因为是要有两个元素,一个是个数,还有一个是具体关键词,然后就找了一下map的具体用法,map.put就可以解决两个元素的变动问题

每一个关键词需要统计个数最后再输出有多少个,就用了一个count来接收关键词的个数,然后导入map中加一即可

map存完了之后就需要输出,然后输出也不会,在CSDN上找到了相应的输出操作:

选择了keySet作为最后的遍历,然后本来想用一个普通的for循环进行迭代输出,可是发现好像输出不了,对于迭代的理解还不够深刻,不知道哪出现了问题,最后还是选择用增强的for循环输出排序后的数据。

在写完题目之后我还找了一下三个接口之间的关系和异同,发现各有各的用处,下面的链接是我学习三个接口的异同的CSDN博客:
https://www.cnblogs.com/liangbaolong/p/12885025.html
我把接口理解成了在c语言中学习的链表的相关知识,很像但是不同,还需要多加学习更多的用法,在后面题目集六中就使用了list对于对象数组的操作,很方便。
3、采坑心得
1、题目集四
-
题目集四很麻烦,三道题的前两道都很麻烦,需要做很久很久,7-2中光是写类就写了很久,然后调试更久, * 在判断两个日期是否相等的时候出现了问题,我当时的判断是这样的:
![]()
直接用一个if语句来判断所有的日期,发现并不能返回正确的格式,然后我就换了个格式,换成了先判断年,在判断月,最后判断天的格式:
![]()
这样之后问题就解决了,可以正确的判断出天数的大小。 -
在计算两个日期之间的天数时,作为一个判断两个日期之间的年份出了错了,排查之后发现一开始的赋值就不对,当时设置的情况是day>date,然后年月日都不同的情况:
![]()
设置了一个测试数据,然后在遍历完测试之后将测试数据返还给最初值,当时设置的测试数据是上图中注释里面的,发现完全不同,逻辑错误,改一下之后就解决了。 -
还是在计算两个日期天数时出现了问题,之前判断的时候没有加上闰年,导致判断天数总是少几天,然后加上了闰年之后又多了几天,最后想是每次判断闰年后要把数组中二月份的天数重新返回成28,这个时候才是解决了,注释中的内容就是具体的操作:
![]()
-
水文数据的题更加复杂,写了三天多才写到38分,满分50,第一个判断连续输入为空就不知道怎么判断,之后在网上查找了以后就发现有正则表达式可以判断是否连续输入为空:
![]()
-
一开始的想法是直接聚在一起判断是否正确,用一整个正则表达式判断,然后发现不行,因为每个数据的两端都会有空格的存在,但是日期时间的那一块数据日期和时间的中间用一个空格分割,就没有办法全部消除空格,然后最后就把每一类的数据用‘|’分开来,然后用trim()的方法消除首尾空格之后就解决了,这个方法还解决了要是不是由‘|’分割的五部分就输出Wrong Format的情况:
![]()
-
还有在判断数据的正确性的时候一开始不知道{}可以用在正则表达式里面来表示可以是多少位到多少位,然后就用‘|’来判断或者的位数,就很麻烦,在最后的情况下有一个数据最后一个带有字母的,按照之前的正则表达式中没办法判断,然后问了同学之后知道了原来还有{}可以用来获取位数,这样就解决了带有字符的问题,带有字符的数据是一个错误数据:
![]()
-
最后判断完数据是否正确之后需要导入实体类,然后还得用到日期方法,最开始的时候没有重新声明日期的方法,就导致无法传入,调试了好久才发现原来要新增加一个声明,声明中的日期随便写一个即可:
![]()
-
然后最后的情况还有两个点没有过,也不知道是哪里错了,而且也没有调试出来
![]()
2、题目集五
- 7-5的日期类,日期跟题目集四之间的没有太大的区别,我只是卡了两个点,就是前n天最大值和后n天最大值,试了很久,运行超时,然后最后找到了一个方法,发现了400年的天数是一样的,就把n除以400的天数再取模,然后再直接在年上面加,然后再用传统方法来判断天数解决:
![]()
- 不然的话会运行超时,设置了一个这个取模之后,运行时间大大减少了,后面的一些方法跟题目集四的方法大致一样。
- 7-4的统计关键词出现的次数,这道题用list接口来获取输入,当我刚刚写完的时候发现大部分的测试点都是错的,像这样:
![]()
- 第五个测试点是注释之中的关键词不能算,所以我选择用了正则表达式来获取注释内容,并且把这些内容替换成空格:
![]()
- 第六个测试点是字符串中的关键词不能算,字符串赋值用的是双引号,所以需要把双引号中的内容替换成空格:
str = str.replaceAll("\\\"(.*\\n)*?.*?\\\"", " "); - 第二个测试点是[]中的关键词也不能算,四则运算之中的关键词同样也是不能算的,只需要把[]中的换成空格,把四则运算的等号随便换成一个字母即可:
![]()
- 这样一些正则表达式替换之后就可以顺利通过,但是还有一个测试点,就是最后的一个正常测试:
![]()
- 这个测试点我个人认为是错误的,我试了很多测试数据都是完全正确,当然也有可能是我自己的代码疏忽,最后的三分没有拿到。
3、题目集六
- 题目集六相对于前两个题目集来说相对简单,踩坑只有在7-5,图形继承与多态这道题上面有,上文说过在网上自学了ArrayList之中的对于对象数组的操作:
![]()
- 看了这个后发现list的方法可以储存对象,然后我就设置了三个对象数组和一个list接口,对象数组加一是因为担心数组越界:
ArrayList<Shape> shape = new ArrayList<>();
Circle[] cir = new Circle[a + 1];
Rectangle[] rec = new Rectangle[b + 1];
Triangle[] tri = new Triangle[c + 1]; - 然后设置了这些发现还有一处没有考虑,那就是输入的数据小于0的时候输出Wrong Format,写了一个语句之后解决:
![]()
4、改进建议
- 圈复杂度,圈复杂度到现在还是超过了10,运用了很多的for语句来循环遍历,以及各种if-else语句来判断数据,还有用到了switch来选择查找,这些是增加圈复杂度的原因,以后需要多多注意把这些语句替换成其他的不增加圈复杂度的语句,现在已经特别注意圈复杂度了,尽量不使用if语句,减少圈复杂度。
- 对类之间的设计方法欠缺,直到现在我写的作业都是按照老师给的类图来写,自己没有设计过类之间的关系,对类之间的关系也没有做到了解,仅仅只是知道和会使用,今后要多多学习对类的设计怎么样做才能做到最好,对类的设计还需要遵循单一职责原则,一个类只能处理一个功能,做到代码的清晰和整洁,代码的优化也需要提高,同时做了这么久的题目,我也清楚的知道现在的我没有办法设计出很好的类关系,基本上都是老师设计我来实现,同时我都会想,要是没有类图,我是一定写不出来的,后续的学习要多多提高。
- 对正则表达式的语法还需要大幅度增加,还是对正则表达式的应用不熟悉,需要多多提高和练习,就像这几次的题目集正则表达式中{}的应用我还不会,但是经过这几次的题目集我就知道了这个方法。
- 速度也需要提高,题目集四和题目集五都是因为没时间了才没有拿到满分,要是我能早点写完其他的稍微简单的题目,然后花大量的时间来测试没过的点,说不定就可以测试出来拿更高的分。
5、总结
- 三次题目集中总是有一道题特别难,以后需要花大量的时间来调试,需要利用一切空余时间,不管怎样都得尽力去完成。
- 老师在这次的几个题目集中加上了一些分值低的,专注练习基础的题目,我觉得这个方法很好,可以有助于我练习基础,特别是正则表达式,这个方法在以后肯定是能多次使用,还有一些对java程序的相关操作和语法我都觉得对我很有用。
- 题目的难易程度我觉得也挺好的,可以让我更加感觉到自己的不足,而且我最后的得分也不低,培养自己的自学能力,和坚持能力。
- 还是跟上次一样,我觉得老师讲解的时候可以再讲的细一点,不需要深入很多,只需要点一下就行了,这样的话我可以省去很多的自学时间,而且老师讲过的在我的记忆里更加的深刻,然后还有这次接触了接口类,这个也是之前老师没有讲过的,也是根据自学才会写,希望老师能稍微讲一点注意事项。

































浙公网安备 33010602011771号