一、前言:

前面三次pta作业是一些基础性练习,主要就是为了让大家对java的面向对象有一些初步了解,让我们明白面向对象的工作原理,以对象为主。因此,题量都不多,难度也不大,要学习的就是对类和对象的作用的辨析,还增加了之前没有接触的正则表达式检验的知识(这部分需自学)。前面两次作业是单个类的题型,第三次主要针对多个类以及其对象的方法的联合,还有正则表达式。

二、分析:

分析代码工具:SourceMonitor是一款免费的代码度量工具,运行在Windows平台下。它可对多种语言写就的代码进行度量,包括C、C++、C#、Java、VB、Delphi和HTML,并且针对不同的语言,输出不同的代码度量值。

像其他代码度量工具一样,SourceMonitor只关注代码,并为编码人员提供及时的反馈,它不是一款项目管理工具,不关注项目实施中从功能分析到设计编码,再到测试这整个过程。

1、题目集1-7.8判断三角形类型

(1)设计与分析:因为是对三角形类型的分析,因此多用几个if-else即可完成对其目标的实现。但是也是因为这样,if-else的数目一多,本程序的复杂度也大大提高,

 

第一次博客作业-分析前三次java作业_正则表达式

 

从上面用到的Source Monitor 分析中可以看出,这个代码的平均圈复杂度为22。显然,if-else贡献了大部分。而且,我们在看的过程也可以发现的是,看起来特别费劲,因为阅读了几行代码就又要分另外的情况,而本题的情况又有多种,于是很容易混乱。

(2)踩坑心得:真的有一点有被无语到,就是平常判断三角形都是用勾股定理,因此博主也是这么想的。就是正常的(a^a+b^b=c^c)嘛,结果尴尬了,它直接给我报错,输出还没有结果。

第一次博客作业-分析前三次java作业_赋值_02

 

 

 然后上网搜了一下,发现需要像下面这么写,貌似是因为它可能是有的情况是趋于0而直接被忽略,正确结果如下。

 

if((a==b&&a!=c)||(a==c&&a!=b)||(b==c&&a!=c)){
                    if(a*a+b*b-c*c<0.000001||a*a+c*c-b*b<0.000001||b*b+c*c-a*a<0.000001)
                        System.out.print("Isosceles right-angled triangle");
                    else 
                        System.out.print("Isosceles triangle");
                }
                else if(a*a+b*b-c*c<0.000001||a*a+c*c-b*b<0.000001||b*b+c*c-a*a<0.000001)
                    System.out.print("Right-angled triangle");
                else
                    System.out.print("General triangle");

 第一次博客作业-分析前三次java作业_正则表达式_03

 本题还有一点需要注意的就是,对于判断是否是三角形时,一定要对三种情况都考虑,不能本能地以为只要满足一种情况下两边之和大于第三边就行。当然了,博主是注意到了的。

(3)改进建议:之前的分析中已经发现了此代码的圈复杂度实在是有点高,因此可以考虑一下对这方面的改进,例如用一个方法来对三角形类型的考虑,而不必直接写一起,这样看起来更有结构性。刚刚本来还想说可以考虑对不同的情况赋值,然后用switch-case对各种情况的结果来反映,但是好像有点会更复杂,所以暂不考虑。

 2、题目集2-7-4求下一天

 (1)设计与分析:题目给出说MAIN方法需包含以下方法,因此也就给了我们一个方向。

 

public static void main(String[] args);//主方法 
public static boolean isLeapYear(int year) ;//判断year是否为闰年,返回boolean类型 
public static boolean checkInputValidity(int year,int month,int day);//判断输入日期是否合法,返回布尔值
public static void nextDate(int year,int month,int day) ; //求输入日期的下一天

于是我们的代码只要都包含这些方法,然后通过类似函数的调用来应用各个方法即可,官方的Source Monitor的分析情况如下。可以发现这一题的满足条件性比上次好一点,只有圈复杂度的值过大,平均复杂度Average Complexity为6.25,较上一题明显降低了很多,而它的最大复杂度Maximum Complexity竟然是14,比平均复杂度Average Complexity大了不少。可以看出的是可能是各个方法的圈复杂度不一样,有的低,有的高,便造成了这之间的差距。

第一次博客作业-分析前三次java作业_面向对象_04

 

 而通过阅读博主的代码,便可以发现真的就是这样的。求下一天的方法明显比另两个更加复杂,它的if-else的个数也更多。

import java.util.Scanner;
public class Main{
    public static boolean isLeapYear(int a) //判断year是否为闰年,返回boolean类型;
    {
        if((a%4==0&&a%100!=0)||a%400==0)
            return true;
        else
            return false;
    }
    public static boolean checkInputValidity(int a,int b,int c)//判断输入日期是否合法,返回布尔值
    {
        if(a<1820||a>2020||b<1||b>12||c>31||c<1||((b==4||b==6||b==9||b==11)&&c==31)||(b==2&&c==30)||(!isLeapYear(a)&&b==2&&c==29))
            return false;
        else
            return true;
    }
    public static void nextDate(int a,int b,int c)  //求输入日期的下一天
    {
        int f,e,g;
        int d[]=new int []{0,31,28,31,30,31,30,31,31,30,31,30,31};
        if(isLeapYear(a))
            d[2]=29;
        if(d[b]==c){
            if(b==12){
                e=a+1;
                f=1;}
            else {e=a;f=b+1;}
            g=1;
        }
        else{e=a;f=b;g=c+1;}
        System.out.print("Next date is:"+e+"-"+f+"-"+g);
    }
    public static void main(String[] args){
        Scanner input=new Scanner(System.in);
        int a=input.nextInt();
        int b=input.nextInt();
        int c=input.nextInt();
        if(!checkInputValidity(a,b,c))
            System.out.print("Wrong Format");
        else
            nextDate(a,b,c);
    }
}

(2)踩坑心得:这一题因为题目给的方法很清楚,因此对于结构方面还好。主要就是方法内部的坑容易被忘记,博主出错的就是求下一天的方法了。对于每个月的最大天数的不同,博主一开始是想通过判断月份,然后用switch-case来分类显示的,但是感觉有点繁琐,于是就用了数组。这个坑就是对于求后一天的时间,很多人都可以想到跨月的情况和闰年的最大天数不同的情况,但是往往容易忽略对于跨年的计算。而对于跨年情况的描写,可能又会有同学表示疑惑了,其实只要判断年份为12月就行了。然后就是别忘了日期是1月1号。

第一次博客作业-分析前三次java作业_面向对象_05

 

 (3)改进及建议:通过工具分析,照样可以发现,博主的代码需要改进的只有圈复杂度了,看完了Source Monitor的分析,发现是

public static boolean checkInputValidity(int year,int month,int day);这个方法的圈复杂度造成的,它的圈复杂度为14,应该是判断合
法条件中的if里面包含的逻辑词太多的缘故。
但是博主已经不知道该怎么改了,所以有看到的同学可以帮个忙哈,可发在评论里,博主会及时
看的。感谢~
3、题目集2-7-5 求前N天 

 (1)设计与分析:虽然这题看起来有难度其实不然,因为这题和7-4要用到的几个方法类似,无非就是把求前一天的方法改成求前几天的方法,因此其它的方法我们可以照搬。而对于它的求前几天和求前一天的思想是一样的,只是要注意它这里还分了往前数和往后推两种,要多考虑一下。用Source Monitor 工具做出的分析图如下:

第一次博客作业-分析前三次java作业_正则表达式_06

 

 由图可知,这题的主要不理想的就是圈复杂度,而通过对每个方法圈复杂度的计算,这题和上题一样,其中一个就是因为判断合法日期的方法public static boolean checkInputValidity(int year,int month,int day)贡献了大量的复杂度,再就是求前几天的方法public static void frontDate(int a,int b,int c,int n),它贡献了12的圈复杂度,而这些出现的原因都是因为if里面的逻辑连接词(&&,||)还有方法里面if的数目都太多了。这肯定是会增加我们阅读的难度。

2)踩坑心得:初看题目是就是觉得感觉这个题目和求前一天的题目异曲同工,于是还是很激动地把相同的方法迁移过去了,然后再修改。但是呢,
这30分总不能是白送的吧,肯定没有这种便宜捡的,果然掉坑里去了。在计算到底是往前数第几天的时候,我凭着直觉想着跨月的情况下,应该用前
一个月的最大天数减去还应该往前推的天数,就是那个还应该往前推的那个负数,但可能觉得负数看着别扭就干脆把其写成了它的相反数,然后负号
没了,却忘了在前面多加一个负号,最后运行答案变成了这样:

 

 显然这个答案是错误的,当时我还找了挺久才发现的,简直了。正确的方法应该是像下面这种改过来的。

public static void frontDate(int a,int b,int c,int n)  //求输入日期的前几天
    {
        int f,e,g;
        int d[]=new int []{0,31,28,31,30,31,30,31,31,30,31,30,31};
        if(isLeapYear(a))
            d[2]=29;
        if(n<=0){
            if(c-n>d[b]){
                if(b!=12){
                    e=a;f=b+1;}
                else{
                    e=a+1;f=1;}
                g=c-n-d[b];
            }
            else{e=a;f=b;g=c-n;}
        }
        else{
            if(c-n<=0){
                if(b==1){e=a-1;f=12;g=d[12]+c-n;}
                else{e=a;f=b-1;g=d[b-1]+c-n;}
            }
            else{e=a;f=b;g=c-n;}
        }
        System.out.print(n+" days ago is:"+e+"-"+f+"-"+g);
    }

(3)改进与建议:因为这题和上一题的换汤不换药的缘故,这里需要改进的也是圈复杂度。但是,上面已经说了博主不知道该怎么改,希望看到此拙作的同学可以给一点建议。除此之外,可以看到这题的函数深度(Block Depth)(函数深度指示函数中分支嵌套的层数)也没有达到最理想的状态,而主要提供函数深度大小的就是求前几天的方法public static void frontDate(int a,int b,int c,int n)。而这里很明显的也是if-else嵌套用了很多个,因为他们的条件是逐层分开,所以博主认为这个是很有必要的,而且分析结果也显示函数深度并没有太过于离谱。

4、题目集3-7-2定义日期类 

(1)分析与设计:此题和题目集2-7-4要实现的是同个目的,只是之前那个只有一个Main类,其它的方法均在Main中,而这题则是有两个类,还
多了一个Date类,所有相关的日期方法均在里面,再在Main里面通过创建新的Date的对象来实现对Date类中方法的调用。题目给出以下类图:

第一次博客作业-分析前三次java作业_面向对象_07第一次博客作业-分析前三次java作业_java_08

 

 

通过类图及解题报告可以看出这题主要是要我们实现类的封装性。从这里对象的每个属性都有两个方法,一个set方法,一个get方法,而让其
的取值不受其他类的对象的干扰。对于属性的赋值又是通过和Date类名相同的方法名实现。而通过对代码度量值的测算,又可以发现此多类的
方法较题目集2-7-4降低了它的平均圈复杂度Average Complexity,可见可以更好地让代码的阅读性提高及结构性增强。但是对于最大圈复杂度
来说,还是判断输入是否合法的方法影响了其取值。
(2)踩坑心得:这题的坑倒是没有太多就是要注意这个方法中有两个Date方法,可能会有人觉得有点多余,但其实并没有,因为其中一个有传参,
而另一个就是一个不传参的类似空白方法。但是在对Date对象的属性赋值时,需要通过对空方法传进去。否则就是会出现以下错误:

 

 (3)改进与建议:暂无,希望各位同学提出建议。

5、题目集3-7-3一元多项式求导(类设计)

(1)设计与分析:此题主要难题就是正则表达式检验的知识需要去自学,当然了网上有很多关于这方面的知识。而通过工具测检,可以发现它的
综合简便度和适宜度还挺好的,就是最大圈复杂度有点出入,主要就是Express类的public void show()方法提供的,因为它需要分不同的情况展
示不同的输入输出格式。

代码如下:

import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Scanner;
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);
        String initExpress = input.nextLine();
        String express = initExpress.replaceAll("\\s", "");
        Express textExpress = new Express(express);
        if (textExpress.check())
            textExpress.show();
        else
            System.out.print("Wrong Format");
    }
}

class Express {
    private String express;
    private String term;
    private String ratStr;// 系数部分
    private String indexStr;// 指数部分
    private BigInteger rat;
    private BigInteger index;
    private ArrayList<String> containTerm = new ArrayList<>();

    public Express(){
        
    }
    public Express(String express) {
        this.express = express;
    }
    public String getExpress(){
        return express;
    }
    public void setExpress(String express){
        this.express = express;
    }
    public String getratStr(){
        return ratStr;
    }
    public void setratStr(String ratStr){
        this.ratStr=ratStr;
    }
    public String getindexStr(){
        return indexStr;
    }
    public void setindexStr(String indexStr){
        this.indexStr=indexStr;
    }
    String termExpress = "([+-]?[1-9][0-9]*)?" + "(\\*?[+-]?x(\\^([+-]?[1-9][0-9]*))?)?";
    String wholeExpress = "(([+-]?[1-9][0-9]*)?" + "(\\*?[+-]?x(\\^([+-]?[1-9][0-9]*))?)?)+";
    String constNum = "[+-]?[0-9]+";
    public boolean check() {
        return Pattern.matches(wholeExpress, express);
    }

    public void show() {
        if (Pattern.matches(constNum, express)) {
            System.out.print("0");
            System.exit(0);
        }
        Pattern p = Pattern.compile(termExpress);
        Matcher m = p.matcher(express);
        int flag = 0;
        while (m.find()) {
            //System.out.println(m.group(2));
            ratStr = m.group(1);
            indexStr = m.group(4);
            if (ratStr != null) {
                rat = new BigInteger(ratStr);
                if (m.group(2) != null && m.group(2).startsWith("-")) {
                    rat = BigInteger.valueOf(-1);
                    //System.out.println(rat);
                }
                else if (m.group(2) != null && m.group(2).startsWith("+")) {
                    rat = BigInteger.valueOf(1);
                    //System.out.println(rat);
                }
            } else {
                rat = BigInteger.valueOf(1);
                if (m.group() != null && m.group().startsWith("-")) {
                    rat = BigInteger.valueOf(-1);
                    //System.out.println(rat);
                }
                else if (m.group() != null && m.group().startsWith("+")) {
                    rat = BigInteger.valueOf(1);
                    //System.out.println(rat);
                }
            }
            if (indexStr != null) {
                index = new BigInteger(indexStr);
                rat = rat.multiply(index);
                index = index.subtract(new BigInteger("1"));
                if (rat.compareTo(new BigInteger("0")) > 0 && flag == 1)
                    System.out.print("+" + (rat.equals(new BigInteger("-1")) ? "-x" : rat + "*x") + (index.equals(new BigInteger("1")) ? "" : ("^" + index)));
                else {
                    flag = 1;
                    System.out.print((rat.equals(new BigInteger("-1")) ? "-x" : rat + "*x") + (index.equals(new BigInteger("1")) ? "" : ("^" + index)));
                }
            } else if (m.group(2) != null) {
                if(flag == 1 && rat.compareTo(new BigInteger("0")) > 0)
                    //System.out.print("+"+rat);
                    System.out.print(rat);
                else{
                    flag = 1;
                    System.out.print(rat);
                }
            }
        }
    }
}

 

(2)踩坑心得:就是在pta上提交的时候最后一个测试点一直过不去,已经在My Eclipsw上测试了各个结果,都发现不了问题,后面就是上网查了一下,发现如下图这种情况是错误的,最后发现中间的“+”号不会输出,应该是下面这种答案。


第一次博客作业-分析前三次java作业_java_09

 

 正确答案:

第一次博客作业-分析前三次java作业_面向对象_10

 (3)改进与建议:

对于题目集三一元多项式求导,仍有太多面向过程的做法,冗余代码过多,复用性低,不利于后期跟进维护。

优化:设计项类,将表达式分解成项,对项进行处理,使用 HashMap建立映射关系,利于运算,利于合并同类项(本题降低了难度并未涉及),降低程序耦合度。

三、总结:
1、发现面向对象的学习相较于面向过程而言,更注重逻辑性与结构性,例如一个对象可以有多个属性,多个方法,它可以做多件事情。而这些做
相同事情的对象又在一个共同的类中。
2、学到了什么:通过本阶段的学习,练习了Java的基本语法,了解了面向对象的思想,练习了正则表达式的使用。
3、课程建议:老师讲课讲的挺好的,就是有的时候讲的知识太多,我们接受起来有点困难。还有就是这个pta作业,感觉很多都是我们先写,自学
然后老师才在课堂上讲的,没有说这种方法不好,就是真的是有难度。