OO第二阶段作业总结

目录

 

一、前言

       (1)第四次作业

         知识点:正则表达式匹配,字符串处理(分割等),不同数据类型之间转化,类结构设计,类之间关系(聚合,继承),子类父类之间继承关系的细节问题,重写(Override)。

           虽然涉及的知识点不多,题目只有三道,但深度不小,代码量不小。尤其是题7-1的解决方案十分繁琐,后两题则是设计较为繁琐,类的数量和关系都比之前复杂得多。

            老师给出的完成情况如图:

            

           

          可以看出,和上一次作业集(第三次作业)一样,平均分相较之前有大幅下跌,跌破了六十分,大部分人集中在六十分以下。第三次和该次作业的难度比第一和第二次均有大幅增加,或者说是复杂繁琐程度的大幅加深使得大部分人无暇应对,十分考验自学和适应能力,以及时间安排管理的能力。

       (2)第五次作业

         知识点:字符串处理(分割,测长度等),数组处理(Arrays类/Collections类(使用List)(动态数组ArrayList/链表LinkedList)),三种基本排序算法(冒泡,选择,插入),正则表达式匹配,Java集合接口(List,Set,Map),类之间关系(聚合)。

           该次作业题量有所增加,增至五题,知识点涵盖也更多更广,不过不乏有前三题这种纯粹考基础的类型,但以题7-4最为繁琐,7-5让我们重新思考类设计的思路。

           难度基本维持之前水平,7-4甚至有小幅加难,但是别的题目考的基础知识也更多。

       (3)第六次作业

         知识点:正则表达式匹配,字符串排序(字符串转字符数组,Arrays类的使用),Collections类的使用,ArrayList常用方法,List和数组,泛型,类设计,类之间继承关系,抽象类的使用,接口的使用,多态的运用。

           该次作业题量表面上增至六题,但实际上考基础的题目更多更简单了,难度降低了不少,前几题基本上写出正则就能过。

           该次作业的重点在于对类之间继承和多态的实际运用,反复运用接口,抽象类,反复Override,提高代码的可维护性,可复用性,进而提高代码质量。以及考验我们对一些新技术(Collections类,List接口,泛型等)更细致的掌握。

二、设计与分析

       在此挑选一些个人认为比较有意义去分析的题目,拿出来分析和总结,既是对我个人能力的进一步提高,更是让这些题目能起到更大的作用,同时还可造福看到这篇Blog的初学者们。

(1)第四次作业

7-2 日期问题面向对象设计(聚合一) (35 分)
 

参考题目7-2的要求,设计如下几个类:DateUtil、Year、Month、Day,其中年、月、日的取值范围依然为:year∈[1900,2050] ,month∈[1,12] ,day∈[1,31] , 设计类图如下:

应用程序共测试三个功能:

  1. 求下n天
  2. 求前n天
  3. 求两个日期相差的天数

注意:严禁使用Java中提供的任何与日期相关的类与方法,并提交完整源码,包括主类及方法(已提供,不需修改)

输入格式:

有三种输入方式(以输入的第一个数字划分[1,3]):

  • 1 year month day n //测试输入日期的下n天
  • 2 year month day n //测试输入日期的前n天
  • 3 year1 month1 day1 year2 month2 day2 //测试两个日期之间相差的天数

输出格式:

  • 当输入有误时,输出格式如下: Wrong Format
  • 当第一个数字为1且输入均有效,输出格式如下:
    year-month-day
     
  • 当第一个数字为2且输入均有效,输出格式如下:
    year-month-day
     
  • 当第一个数字为3且输入均有效,输出格式如下:
    天数值

 PTA已提交的源码如下:

 

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            int choose = input.nextInt();
            switch (choose) {
            case 1:
                int y1 = input.nextInt();
                int m1 = input.nextInt();
                int d1 = input.nextInt();
                DateUtil dateutil1 = new DateUtil(d1,m1,y1);
                int n1 = input.nextInt();
                if (dateutil1.checkInputValidity()) {
                    System.out.println(dateutil1.getNextNDays(n1).showDate());
                }
                else {
                    System.out.println("Wrong Format");
                }
                break;
            case 2:
                int y2 = input.nextInt();
                int m2 = input.nextInt();
                int d2 = input.nextInt();
                DateUtil dateutil2 = new DateUtil(d2,m2,y2);
                int n2 = input.nextInt();
                if (dateutil2.checkInputValidity()) {
                    System.out.println(dateutil2.getPreviousNDays(n2).showDate());
                }
                else {
                    System.out.println("Wrong Format");
                }
                
                break;
            case 3:
                int y3 = input.nextInt();
                int m3 = input.nextInt();
                int d3 = input.nextInt();
                DateUtil dateutil3 = new DateUtil(d3,m3,y3);
                int y4 = input.nextInt();
                int m4 = input.nextInt();
                int d4 = input.nextInt();
                DateUtil dateutil4 = new DateUtil(d4,m4,y4);
                int r = dateutil3.getDaysofDates(dateutil3)-dateutil4.getDaysofDates(dateutil4);
                if (r<0) {
                    r=-r;
                }
                if (dateutil3.checkInputValidity()&&dateutil4.checkInputValidity()) {
                    System.out.println(r);
                }
                else {
                    System.out.println("Wrong Format");
                }
                
                break;
            default:
                System.out.println("Wrong Format");
            }
            
        }
        finally {
            input.close();
        }
    }
}
class DateUtil {
    private Day day;
    
    public DateUtil() {
        super();
        // TODO Auto-generated constructor stub
    }
    public DateUtil(int d, int m, int y) {
        super();
        this.day = new Day(y,m,d);
    }
    public Day getDay() {
        return day;
    }
    public void setDay(Day day) {
        this.day = day;
    }
    public boolean checkInputValidity() {
        if (this.day.validate()) {
            return true;
        }
        else {
            return false;
        }
    }
    public boolean compareDates(DateUtil date) {
        return true;
    }
    public boolean equalTwoDates(DateUtil date) {
        if (date.getDay().getMonth().getYear().getValue()==day.getMonth().getYear().getValue()&&date.getDay().getMonth().getValue()==day.getMonth().getValue()&&date.getDay().getValue()==day.getValue()) {
            return true;
        }
        else {
            return false;
        }        
    }
    public String showDate() {
        return this.day.getMonth().getYear().getValue()+"-"+this.day.getMonth().getValue()+"-"+this.day.getValue();
    }
    public DateUtil getNextNDays(int n) {
        int[] maxDay = {31,28,31,30,31,30,31,31,30,31,30,31};

        int day = this.getDay().getValue();
        int month = this.getDay().getMonth().getValue();
        int year = this.getDay().getMonth().getYear().getValue();
        day+=n;
        this.getDay().setValue(day);
        while (day>maxDay[month-1]) {
            if ((year%4==0&&year%100!=0)||(year%400==0))
                maxDay[1]=29;
            else
                maxDay[1]=28;
            if (month<12) {
                month++;
                this.getDay().getMonth().setValue(month);
                day=day-maxDay[month-2];
                this.getDay().setValue(day);
            }
            else {
                month=1;
                this.getDay().getMonth().setValue(month);
                year++;
                this.getDay().getMonth().getYear().setValue(year);
                day=day-maxDay[month-1];
                this.getDay().setValue(day);
            }
        }
        return this;
    }
    public DateUtil getPreviousNDays(int n) {
        int[] maxDay = {31,28,31,30,31,30,31,31,30,31,30,31};
        if (this.getDay().getMonth().getYear().isLeapYear())
            maxDay[1]=29;
        else
            maxDay[1]=28;
        int day = this.getDay().getValue();
        int month = this.getDay().getMonth().getValue();
        int year = this.getDay().getMonth().getYear().getValue();
        day-=n;
        this.getDay().setValue(day);
        if (day<=0) {
            if (month>1) {
                month--;
                this.getDay().getMonth().setValue(month);
                day=maxDay[month-1]-(-day);
                this.getDay().setValue(day);
            }    
            else {
                month=12;
                this.getDay().getMonth().setValue(month);
                day=maxDay[month-1]-(-day);
                this.getDay().setValue(day);
                year--;
                this.getDay().getMonth().getYear().setValue(year);
            }
        }
        return this;
    }
    public int getDaysofDates(DateUtil date) {
        int[] maxDay = {31,28,31,30,31,30,31,31,30,31,30,31};
        if (date.getDay().getMonth().getYear().isLeapYear())
            maxDay[1]=29;
        else
            maxDay[1]=28;
        int day = date.getDay().getValue();
        int month = date.getDay().getMonth().getValue();
        int year = date.getDay().getMonth().getYear().getValue();
        int result=0;
        for (int i=1;i<year;i++,result+=365) {
            if ((i%4==0&&i%100!=0)||(i%400==0)) {
                result++;
            }
        }
        for (int i=1;i<month&&month>1;i++) {
            result+=maxDay[i-1];
        }
        result+=day;
        return result;        
    }
}
class Day {
    private int value;
    private Month month;
    private int[] maxNum = {31,28,31,30,31,30,31,31,30,31,30,31};
    
    public Day() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Day(int yearValue, int monthValue, int value) {
        super();
        month = new Month(yearValue,monthValue);
        this.value = value;
    }
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    public Month getMonth() {
        return month;
    }
    public void setMonth(Month value) {
        this.month = value;
    }
    public void resetMin() {
        this.value = 1;
    }
    public void setMax() {
        if (month.getYear().isLeapYear()) {
            this.maxNum[1]++;
        }
        this.value = maxNum[month.getValue()-1];
    }
    public boolean validate() {
        if (this.month.getYear().isLeapYear()) {
            this.maxNum[1]++;
        }
        if (this.value>=1&&this.value<=31&&this.month.validate()&&this.value<=maxNum[this.month.getValue()-1]) {
            return true;
        }    
        else {
            return false;
        }    
    }
    public void dayIncrement() {
        if (validate()) {
            value++;
        }
    }
    public void dayReduction() {
        if (validate()) {
            value--;
        }
    }
}
class Month {
    private int value;
    private Year year;
    
    public Month() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Month(int yearValue, int value) {
        super();
        year = new Year(yearValue);
        this.value = value;
    }
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    public Year getYear() {
        return year;
    }
    public void setYear(Year year) {
        this.year = year;
    }
    public void resetMin() {
        this.value = 1;
    }
    public void resetMax() {
        this.value = 12;
    }
    public boolean validate() {
        if (this.value>=1&&this.value<=12&&this.year.validate()) {
            return true;
        }
        else {
            return false;
        }
    }
    public void monthIncrement() {
        if (validate()) {
            value++;
        }
    }
    public void monthReduction() {
        if (validate()) {
            value--;
        }
    }
}
class Year {
    private int value;
    
    public Year() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Year(int value) {
        super();
        this.value = value;
    }
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    public boolean isLeapYear() {
        if ((value%4==0&&value%100!=0)||(value%400==0))
            return true;
        else
            return false;
    }
    public boolean validate() {
        if (this.value>=1900&&this.value<=2050) {
            return true;
        }
        else {
            return false;
        }
    }
    public void yearIncrement() {
        if (validate()) {
            value++;
        }
    }
    public void yearReduction() {
        if (validate()) {
            value--;
        }
    }
}

该代码按照题目要求设计DateUtil、Year、Month、Day类,主类用主方法关联(聚合)调用DateUtil类,DateUtil类关联Day类,Day再顺着关联Month类,Month类再关联Year类,属于一条龙的链式结构。

主类使用switch语句实现数据读取和一部分的数据校验,数据用带参构造方法的参数传入DateUtil类,该类的getNextNDays(int),getPreviousNDays(int)和getDaysofDates(DateUtil)方法为解决题目所提要求的三个最基本的方法,showDate()方法负责格式化输出,compareDates(DateUtil)和equalTwoDates(DateUtil)基本没用到,checkInputValidity()又负责一部分的数据校验。DateUtil类在接收完数据做完份内的校验后把年月日数据紧接着接力传给Day类。Day类等(或者说包含Day在内的年月日三个类基本都是这个套路)都包含value属性(代表年/月/日对应的值),Year类中多一个方法用于判断闰年,Day类还多一个maxNum[]数组属性用于存储每个月的最大天数。这些类包含resetMin()和setMax()方法(基本没用到),validate()方法用于负责一部分的数据校验(是哪个类就负责哪个部分),还有dayIncrement()和dayReduction()这样的工具方法负责对value进行简单加减操作。Day类取下日数据,再把年月数据接力给Month类,Month类取下月数据,最后把年数据给Year类,至此传输数据完成,程序通过switch语句的选择执行DateUtil中对应的方法(下n天,前n天,相差天数),求出不同的值,格式化输出得到结果。

 

该代码已经实现了题目要求的类关系设计,类图如下所示:

 

 可以看到已经完美还原了题目要求的关系设计。

 使用SourceMonitor分析如下:

最大复杂度中中等等,有点偏高,应该是大量使用了if语句的原因,虽然明面上嵌套的基本没有,但是在执行过程中在类和方法之间反复跳转,应该会发生”伪嵌套“,尤其是数据校验的判断被分成了很多小块,可能和这个有关系。

 

 

7-3 图形继承 (15 分)
 

编写程序,实现图形类的继承,并定义相应类对象并进行测试。

  1. 类Shape,无属性,有一个返回0.0的求图形面积的公有方法public double getArea();//求图形面积
  2. 类Circle,继承自Shape,有一个私有实型的属性radius(半径),重写父类继承来的求面积方法,求圆的面积
  3. 类Rectangle,继承自Shape,有两个私有实型属性width和length,重写父类继承来的求面积方法,求矩形的面积
  4. 类Ball,继承自Circle,其属性从父类继承,重写父类求面积方法,求球表面积,此外,定义一求球体积的方法public double getVolume();//求球体积
  5. 类Box,继承自Rectangle,除从父类继承的属性外,再定义一个属性height,重写父类继承来的求面积方法,求立方体表面积,此外,定义一求立方体体积的方法public double getVolume();//求立方体体积
  6. 注意:
  • 每个类均有构造方法,且构造方法内必须输出如下内容:Constructing 类名
  • 每个类属性均为私有,且必须有getter和setter方法(可用Eclipse自动生成)
  • 输出的数值均保留两位小数

主方法内,主要实现四个功能(1-4): 从键盘输入1,则定义圆类,从键盘输入圆的半径后,主要输出圆的面积; 从键盘输入2,则定义矩形类,从键盘输入矩形的宽和长后,主要输出矩形的面积; 从键盘输入3,则定义球类,从键盘输入球的半径后,主要输出球的表面积和体积; 从键盘输入4,则定义立方体类,从键盘输入立方体的宽、长和高度后,主要输出立方体的表面积和体积;

假如数据输入非法(包括圆、矩形、球及立方体对象的属性不大于0和输入选择值非1-4),系统输出Wrong Format

输入格式:

共四种合法输入

  • 1 圆半径
  • 2 矩形宽、长
  • 3 球半径
  • 4 立方体宽、长、高

输出格式:

按照以上需求提示依次输出

 

  PTA已提交的源码如下: 

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            int choose = input.nextInt();
            switch (choose) {
            case 1:
                double radius = input.nextDouble();
                if (radius>0) {
                    Circle circle = new Circle(radius);
                    System.out.printf("Circle's area:%.2f\n",circle.getArea());
                }
                else {
                    System.out.println("Wrong Format");
                }
                break;
            case 2:
                double width = input.nextDouble();
                double length = input.nextDouble();
                if (width>0&&length>0) {
                    Rectangle rectangle = new Rectangle(width,length);
                    System.out.printf("Rectangle's area:%.2f\n",rectangle.getArea());
                }
                else {
                    System.out.println("Wrong Format");
                }
                break;
            case 3:
                double radius1 = input.nextDouble();
                if (radius1>0) {
                    Ball ball = new Ball();
                    ball.setRadius(radius1);
                    System.out.printf("Ball's surface area:%.2f\n",ball.getArea());
                    System.out.printf("Ball's volume:%.2f",ball.getVolume());
                }
                else {
                    System.out.println("Wrong Format");
                }
                break;
            case 4:
                double width1 = input.nextDouble();
                double length1 = input.nextDouble();
                double height = input.nextDouble();
                if (width1>0&&length1>0&&height>0) {
                    Box box = new Box();
                    box.setWidth(width1);
                    box.setLength(length1);
                    box.setHeight(height);
                    System.out.printf("Box's surface area:%.2f\n",box.getArea());
                    System.out.printf("Box's volume:%.2f",box.getVolume());
                }
                else {
                    System.out.println("Wrong Format");
                }
                break;
            default:
                System.out.println("Wrong Format");
            }
        }
        finally {
            input.close();
        }
    }
}
class Shape {
    public double getArea() {
        return 0.0;
    }

    public Shape() {
        super();
        // TODO Auto-generated constructor stub
        System.out.println("Constructing Shape");
    }
    
}
class Circle extends Shape {
    protected double radius;
    @Override
    public double getArea() {
        return Math.PI*radius*radius;
    }
    public Circle() {
        super();
        // TODO Auto-generated constructor stub
        System.out.println("Constructing Circle");
    }
    public Circle(double radius) {
        super();
        System.out.println("Constructing Circle");
        this.radius = radius;
    }
    public double getRadius() {
        return radius;
    }
    public void setRadius(double radius) {
        this.radius = radius;
    }
    
    
}
class Rectangle extends Shape {
    protected double width;
    protected double length;
    @Override
    public double getArea() {
        return width*length;
    }
    public Rectangle() {
        super();
        // TODO Auto-generated constructor stub
        System.out.println("Constructing Rectangle");
    }
    public Rectangle(double width, double length) {
        super();
        System.out.println("Constructing Rectangle");
        this.width = width;
        this.length = length;
    }
    public double getWidth() {
        return width;
    }
    public void setWidth(double width) {
        this.width = width;
    }
    public double getLength() {
        return length;
    }
    public void setLength(double length) {
        this.length = length;
    }
    
    
}
class Ball extends Circle {
    @Override
    public double getArea() {
        return 4*Math.PI*radius*radius;
    }
    public double getVolume() {
        return Math.PI*radius*radius*radius*4.0/3.0;//不知道为什么顺序不可调换
    }
    public Ball() {
        super();
        // TODO Auto-generated constructor stub
        System.out.println("Constructing Ball");
    }
    
}
class Box extends Rectangle {
    private double height;
    
    public Box() {
        super();
        // TODO Auto-generated constructor stub
        System.out.println("Constructing Box");
    }
    
    public Box(double height) {
        super();
        System.out.println("Constructing Box");
        this.height = height;
    }
    public Box(double width, double length) {
        super(width, length);
        // TODO Auto-generated constructor stub
    }
    
    public double getHeight() {
        return height;
    }

    public void setHeight(double height) {
        this.height = height;
    }

    @Override
    public double getArea() {
        return width*length*2+width*height*2+length*height*2;
    }
    public double getVolume() {
        return width*length*height;
    }
    
}

该题主要目的是熟悉继承这种类关系,体会继承和多态这种面向对象设计思想。为此,该代码遵循题目要求,使用主类读取数据及校验数据,关联Circle,Rectangle,Ball,Box四个类,数据通过带参构造方法和Setter方法传入这几个类,Circle类和Rectangle类都继承于Shape类,Ball类继承于Circle类(都只需要一个半径属性),Box类继承于Rectangle类(只需要加一个高度属性变成三维)。Shape类内含一个getArea()方法,默认什么都不做返回0.0,每个子类都在继承的同时将它用Override重写为各自的求面积方法,子类发生向上转型,进行方法覆盖,使用自己的属性进行计算,返回各自的结果,实现了继承和多态。三维的Ball类和Box类同理,并在重写求表面积方法后新增了一个求体积方法。

值得注意的是,此处本人运用多态不熟练,方法覆盖使用的(子类)对象引用了本类(子类)实例,仅体现了编译时多态,未体现运行时多态(动态绑定),不存在父类引用指向子类对象。这也导致之前多次踩到一些坑,程序无法正常传入传出数据,原因应该就出在父类对象访问不了子类对象这一块,加上当时也不熟悉super.可以访问父类方法,导致对象之间的访问出现了混乱。

最终实现的类图如下:

可以看到,完美还原了题目要求。

SourceMonitor分析如下:

 最大复杂度偏高了一点,推断只可能是数据校验做的不够好,暂未发现其它特别容易增加复杂度的地方。但怀疑反复的继承和二重继承可能会增加深度,影响复杂度等参数?

 

 

(2)第五次作业

 

7-4 统计Java程序中关键词的出现次数 (25 分)
 

编写程序统计一个输入的Java源码中关键字(区分大小写)出现的次数。说明如下:

  • Java中共有53个关键字(自行百度)
  • 从键盘输入一段源码,统计这段源码中出现的关键字的数量
  • 注释中出现的关键字不用统计
  • 字符串中出现的关键字不用统计
  • 统计出的关键字及数量按照关键字升序进行排序输出
  • 未输入源码则认为输入非法

输入格式:

输入Java源码字符串,可以一行或多行,以exit行作为结束标志

输出格式:

  • 当未输入源码时,程序输出Wrong Format
  • 当没有统计数据时,输出为空
  • 当有统计数据时,关键字按照升序排列,每行输出一个关键字及数量,格式为数量\t关键字

 PTA已提交的源码如下: 

import java.util.*;
import java.util.regex.*;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
                String[] str = new String[]{"abstract","default","goto","null","switch","boolean","do","if","package","nchronzed","break","double","implements","private","this","byte","else","import","protected","throw","throws","case","extends","instanceof","public","transient","catch","false","int","return","true","char","final","interface","short","try","class","finally","long","static","void","const","float","native","strictfp","volatile","continue","for","new","super","while","assert","enum"};
                List<String> key = Arrays.asList(str);
                boolean judge = true;
                int flag = 0;
                while (judge) {
                    String inp = input.nextLine();
                    for (int i=0;i<key.size();i++) {
                        String Reg = key.get(i);
                        Pattern pattern = Pattern.compile(Reg);
                        Matcher matcher = pattern.matcher(inp);
                        if (matcher.find()) {
                            flag = 1;
                        }
                    }
                    if (flag==0) {
                        System.out.println("Wrong Format");
                    }
                    if (input.nextLine().equals("exit")) {
                        judge = false;
                    }
                }
        }
        finally {
            input.close();
        }
    }
}

非常明显,这又是一段未完成的代码(捂脸

当时忙着准备蓝桥杯,先秒杀了其他题目,把这题原封不动留到了最后一个晚上,未曾想低估了题目复杂程度,有亿点麻烦,写到一半DDL就到了...可见拖拉的危害性,在第六次作业中我就吸取教训,直接一口气把六道题基本全部写完了再慢慢小修小改。

但是这题的整体思路是已经出来了的。事先把关键字存进List(这里没想用Map是因为不熟悉,日后会尝试),一行行输入,每一行都用正则匹配和split,replace,replaceAll等去处理代码,达到把关键字提出来的效果,之后再去和List中的关键字做匹配并计数。

 该题中由于使用的是List(List<String> key声明,而后使用ArrayList实现类)这种比较熟悉的集合框架,在后期遍历的时候可以直接填入index使用.get方法取key中的元素内容进行匹配和计数,和数组操作类似,没有涉及到很多新知识。

根据查找的资料,集合框架包含结构如下:

 

 

 

 本题若用Map,可以把每一行的字符串分割开,存入Map中,利用Key和Value之间的对应关系,到总关键字表中查找匹配键(关键字)的集合,再由键找值,即确定了每个关键字所在的位置,一样可以实现对关键字的查找标记。由于底层是hash散列表实现的,遍历和查找都会比一般的List更快。

 

 

7-5 日期问题面向对象设计(聚合二) (40 分)
 

参考题目7-3的要求,设计如下几个类:DateUtil、Year、Month、Day,其中年、月、日的取值范围依然为:year∈[1820,2020] ,month∈[1,12] ,day∈[1,31] , 设计类图如下:

类图.jpg

应用程序共测试三个功能:

  1. 求下n天
  2. 求前n天
  3. 求两个日期相差的天数

注意:严禁使用Java中提供的任何与日期相关的类与方法,并提交完整源码,包括主类及方法(已提供,不需修改)

输入格式:

有三种输入方式(以输入的第一个数字划分[1,3]):

  • 1 year month day n //测试输入日期的下n天
  • 2 year month day n //测试输入日期的前n天
  • 3 year1 month1 day1 year2 month2 day2 //测试两个日期之间相差的天数

输出格式:

  • 当输入有误时,输出格式如下: Wrong Format
  • 当第一个数字为1且输入均有效,输出格式如下:
    year1-month1-day1 next n days is:year2-month2-day2
     
  • 当第一个数字为2且输入均有效,输出格式如下:
    year1-month1-day1 previous n days is:year2-month2-day2
     
  • 当第一个数字为3且输入均有效,输出格式如下:
    The days between year1-month1-day1 and year2-month2-day2 are:值

PTA已提交的源码如下: 

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            int choose = input.nextInt();
            switch (choose) {
            case 1:
                int y1 = input.nextInt();
                int m1 = input.nextInt();
                int d1 = input.nextInt();
                DateUtil dateutil1 = new DateUtil(y1,m1,d1);
                int n1 = input.nextInt();
                if (dateutil1.checkInputValidity()) {
                    System.out.printf("%d-%d-%d next %d days is:%s",dateutil1.getYear().getValue(),dateutil1.getMonth().getValue(),dateutil1.getDay().getValue(),n1,dateutil1.getNextNDays(n1).showDate());
                }
                else {
                    System.out.println("Wrong Format");
                }
                break;
            case 2:
                int y2 = input.nextInt();
                int m2 = input.nextInt();
                int d2 = input.nextInt();
                DateUtil dateutil2 = new DateUtil(y2,m2,d2);
                int n2 = input.nextInt();
                if (dateutil2.checkInputValidity()) {
                    System.out.printf("%d-%d-%d previous %d days is:%s",dateutil2.getYear().getValue(),dateutil2.getMonth().getValue(),dateutil2.getDay().getValue(),n2,dateutil2.getPreviousNDays(n2).showDate());
                }
                else {
                    System.out.println("Wrong Format");
                }
                
                break;
            case 3:
                int y3 = input.nextInt();
                int m3 = input.nextInt();
                int d3 = input.nextInt();
                DateUtil dateutil3 = new DateUtil(y3,m3,d3);
                int y4 = input.nextInt();
                int m4 = input.nextInt();
                int d4 = input.nextInt();
                DateUtil dateutil4 = new DateUtil(y4,m4,d4);
                int r = dateutil3.getDaysofDates(dateutil3)-dateutil4.getDaysofDates(dateutil4);
                if (r<0) {
                    r=-r;
                }
                if (dateutil3.checkInputValidity()&&dateutil4.checkInputValidity()) {
                    System.out.printf("The days between %d-%d-%d and %d-%d-%d are:%d",dateutil3.getYear().getValue(),dateutil3.getMonth().getValue(),dateutil3.getDay().getValue(),dateutil4.getYear().getValue(),dateutil4.getMonth().getValue(),dateutil4.getDay().getValue(),r);
                }
                else {
                    System.out.println("Wrong Format");
                }
                
                break;
            default:
                System.out.println("Wrong Format");
            }
            
        }
        finally {
            input.close();
        }
    }
}
class DateUtil {
    private Year year;
    private Month month;
    private Day day;
    private int[] maxNum = {31,28,31,30,31,30,31,31,30,31,30,31};
    
    public DateUtil() {
        super();
        // TODO Auto-generated constructor stub
    }
    public DateUtil(int y, int m, int d) {
        super();
        this.year = new Year(y);
        this.month = new Month(m);
        this.day = new Day(d);
    }
    
    public Year getYear() {
        return year;
    }
    public void setYear(Year year) {
        this.year = year;
    }
    public Month getMonth() {
        return month;
    }
    public void setMonth(Month month) {
        this.month = month;
    }
    public Day getDay() {
        return day;
    }
    public void setDay(Day day) {
        this.day = day;
    }
    public void setDayMin() {
        this.day.setValue(1);
    }
    public void setDayMax() {
        if (this.year.isLeapYear()) {
            this.maxNum[1]++;
        }
        this.day.setValue(maxNum[month.getValue()-1]);
    }
    public boolean checkInputValidity() {
        if (this.year.isLeapYear()) {
            this.maxNum[1]++;
        }
        if (this.day.getValue()>=1&&this.day.getValue()<=31&&this.month.validate()&&this.year.validate()&&this.day.getValue()<=maxNum[this.month.getValue()-1]) {
            return true;
        }    
        else {
            return false;
        }    
    }
    public DateUtil getNextNDays(int n) {
        int day = this.day.getValue();
        int month = this.month.getValue();
        int year = this.year.getValue();
        int temp = day;
        if (n<Integer.MAX_VALUE) {
            day+=n;//整型数最大值问题出在这一步 该处分支仅为不完美的初次改进
        }
        else if (n==Integer.MAX_VALUE) {
            day=n;
        }
        this.day.setValue(day);
        while (day>maxNum[month-1]) {
            if (this.year.isLeapYear()) {
                maxNum[1]=29;
            }    
            else {
                maxNum[1]=28;
            }    
            if (month<12) {
                month++;
                this.month.setValue(month);
                day=day-maxNum[month-2];
                this.day.setValue(day);
            }
            else {
                month=1;
                this.month.setValue(month);
                year++;
                this.year.setValue(year);
                day=day-maxNum[month-1];
                this.day.setValue(day);
            }
        }
        if (n==Integer.MAX_VALUE) {
            this.day.setValue(day+temp);
        }
        return this;
    }
    public DateUtil getPreviousNDays(int n) {
        if (this.year.isLeapYear())
            maxNum[1]=29;
        else
            maxNum[1]=28;
        int day = this.day.getValue();
        int month = this.month.getValue();
        int year = this.year.getValue();
        day-=n;
        this.day.setValue(day);
        while (day<=0) {
            if (this.year.isLeapYear())
                maxNum[1]=29;
            else
                maxNum[1]=28;
            if (month>1) {
                month--;
                this.month.setValue(month);
                day=maxNum[month-1]-(-day);
                this.day.setValue(day);
            }    
            else {
                month=12;
                this.month.setValue(month);
                day=maxNum[month-1]-(-day);
                this.day.setValue(day);
                year--;
                this.year.setValue(year);
            }
        }
        return this;
    }
    public boolean compareDates(DateUtil date) {
        return true;
    }
    public boolean equalTwoDates(DateUtil date) {
        if (date.year.getValue()==year.getValue()&&date.month.getValue()==month.getValue()&&date.day.getValue()==day.getValue()) {
            return true;
        }
        else {
            return false;
        }        
    }
    public String showDate() {
        return this.year.getValue()+"-"+this.month.getValue()+"-"+this.day.getValue();
    }
    public int getDaysofDates(DateUtil date) {
        if (date.year.isLeapYear())
            maxNum[1]=29;
        else
            maxNum[1]=28;
        int day = date.day.getValue();
        int month = date.month.getValue();
        int year = date.year.getValue();
        int result=0;
        for (int i=1;i<year;i++,result+=365) {
            if ((i%4==0&&i%100!=0)||(i%400==0)) {
                result++;
            }
        }
        for (int i=1;i<month&&month>1;i++) {
            result+=maxNum[i-1];
        }
        result+=day;
        return result;        
    }
}
class Day {
    private int value;
    
    public Day() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public Day(int value) {
        super();
        this.value = value;
    }

    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    
    public void dayIncrement() {
        value++;
    }
    public void dayReduction() {
        value--;
    }
}
class Month {
    private int value;
    
    public Month() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public Month(int value) {
        super();
        this.value = value;
    }

    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    public void resetMin() {
        this.value = 1;
    }
    public void resetMax() {
        this.value = 12;
    }
    public boolean validate() {
        if (this.value>=1&&this.value<=12) {
            return true;
        }
        else {
            return false;
        }
    }
    public void monthIncrement() {
        if (validate()) {
            value++;
        }
    }
    public void monthReduction() {
        if (validate()) {
            value--;
        }
    }
}
class Year {
    private int value;
    
    public Year() {
        super();
        // TODO Auto-generated constructor stub
    }
    public Year(int value) {
        super();
        this.value = value;
    }
    public int getValue() {
        return value;
    }
    public void setValue(int value) {
        this.value = value;
    }
    public boolean isLeapYear() {
        if ((value%4==0&&value%100!=0)||(value%400==0))
            return true;
        else
            return false;
    }
    public boolean validate() {
        if (this.value>=1820&&this.value<=2020) {
            return true;
        }
        else {
            return false;
        }
    }
    public void yearIncrement() {
        if (validate()) {
            value++;
        }
    }
    public void yearReduction() {
        if (validate()) {
            value--;
        }
    }
}

 该题所要实现的需求和第四次作业7-2题相差无几,都是求下n天,前n天,相差天数,最大的差别在题目所要求的类图上,也就是类之间的关系设计上。

该代码生成的类图如下,可见已经达到题目要求:

该代码主类将输入数据作为参数,使用带参构造方法传入DateUtil类中,先一口气校验完,再分别调用DateUtil类中的不同方法,输出不同的结果。DateUtil类和Day,Month,Year类都是关联(聚合)关系,起到一个中转站的作用,在年月日数据传入DateUtil类之后,依旧是使用带参构造方法将其分别传入对应的类中,除此之外,DateUtil类中还包含一个数组属性maxNum[],用于存储每个月的最大天数。该类的checkInputValidity()方法用于校验输入数据,getNextNDays(int)求下n天,getPreviousNDays(int)求前n天,getDaysofDates(DateUtil)求相差天数,compareDates(DateUtil)比较两个日期大小(未用),equalTwoDates(DateUtil)判断两个日期是否相等,showDate()负责格式化输出。而Day,Month,Year类中都有一个属性value,即各自的值。Year中多一个方法用于判断闰年。

从这里我们已经可以看出,该题的类设计虽然和第四次作业一样都是叫“聚合类设计”,但结构完全不同,该题是输入后一股脑校验完分发完,之后年月日三个类各自存好自己的值,各做各该的事情,不再是之前的那种连锁输入,层层剥开,传一个值都要像剥洋葱一样一层层去掉,简单的一个校验都要分成好几份活,从头到尾又从尾到头反复去调用去折腾,互相调用更是要遵循getDay/getMonth/getYear方法自上而下一条龙调用,方向都不能弄反,代码看上去都十分繁琐,可读性很差。本人认为,这种非连锁式而是一个中转站带N个分布式的结构,不再有链式的那种高度依赖的关系,换句话说就是依赖性减少了,更方便我们去修改各个类的细节职责,乃至修改整体结构去适应不同的需求,而非在一条路上走到死。

使用SourceMonitor分析代码如下:

这是之前第四次作业7-2题目的分析结果:

 可以看出,最大复杂度并未因为类结构的改动而减少,之前的猜想貌似不攻自破,留下了一个较难理解的问题。我的新猜测是,影响复杂度的还是算法问题,也就是说我那几个求解题目问题的方法的算法没改动,所以复杂度没动,和类结构关系不大。

 

 

(3)第六次作业

 

7-5 图形继承与多态 (50 分)
 

掌握类的继承、多态性及其使用方法。具体需求参见作业指导书。

2021-OO第06次作业-5指导书V1.0.pdf

输入格式:

从键盘首先输入三个整型值(例如a b c),分别代表想要创建的Circle、Rectangle及Triangle对象的数量,然后根据图形数量继续输入各对象的属性值(均为实型数),数与数之间可以用一个或多个空格或回车分隔。

输出格式:

  1. 如果图形数量非法(小于0)或图形属性值非法(数值小于0以及三角形三边关系),则输出Wrong Format
  2. 如果输入合法,则正常输出,输出内容如下(输出格式见输入输出示例):
  • 各个图形的面积;
  • 所有图形的面积总和;
  • 排序后的各个图形面积;
  • 再次所有图形的面积总和。

 PTA已提交的源码如下: 

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            int a = input.nextInt();
            int b = input.nextInt();
            int c = input.nextInt();
            if (a<0||b<0||c<0) {
                System.out.println("Wrong Format");
                System.exit(0);
            }
            List<Shape> list= new ArrayList<Shape>();
            for (int i=0;i<a;i++) {
                double r = input.nextDouble();
                Circle circle = new Circle(r);
                if (!circle.validate()) {
                    System.out.println("Wrong Format");
                    System.exit(0);
                }
                list.add(circle);
            }
            for (int i=0;i<b;i++) {
                double width = input.nextDouble();
                double length = input.nextDouble();
                Rectangle rectangle = new Rectangle(width,length);
                if (!rectangle.validate()) {
                    System.out.println("Wrong Format");
                    System.exit(0);
                }
                list.add(rectangle);
            }
            for (int i=0;i<c;i++) {
                double side1 = input.nextDouble();
                double side2 = input.nextDouble();
                double side3 = input.nextDouble();
                Triangle triangle = new Triangle(side1,side2,side3);
                if (!triangle.validate()) {
                    System.out.println("Wrong Format");
                    System.exit(0);
                }
                list.add(triangle);
            }
            System.out.println("Original area:");
            double sumArea = 0;
            for (Shape e:list) {
                System.out.printf("%.2f ",e.getArea());
                sumArea+=e.getArea();
            }
            System.out.printf("\nSum of area:%.2f\n",sumArea);
            System.out.println("Sorted area:");
            Collections.sort(list,new Comparator<Shape>() {
                @Override
                public int compare(Shape shape1,Shape shape2) {
                    return new Double(shape1.getArea()).compareTo(new Double(shape2.getArea()));
                }
            });
            for (Shape e:list) {
                System.out.printf("%.2f ",e.getArea());
            }
            System.out.printf("\nSum of area:%.2f\n",sumArea);
        }
        finally {
            input.close();
        }
    }
}
abstract class Shape {
    abstract public double getArea();
    abstract public boolean validate();
    abstract public String toString();
}
class Circle extends Shape {
    private double radius;
    
    public Circle() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public Circle(double radius) {
        super();
        this.radius = radius;
    }
    

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI*radius*radius;
    }
    @Override
    public boolean validate() {
        if (radius<=0) {
            return false;
        }
        return true;
    }
    @Override
    public String toString() {
        return "Circle [radius=" + radius + "]";
    }
    
}
class Rectangle extends Shape {
    private double width;
    private double length;
    
    public Rectangle() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public Rectangle(double width, double length) {
        super();
        this.width = width;
        this.length = length;
    }
    
    public double getWidth() {
        return width;
    }

    public void setWidth(double width) {
        this.width = width;
    }

    public double getLength() {
        return length;
    }

    public void setLength(double length) {
        this.length = length;
    }

    @Override
    public double getArea() {
        return width*length;
    }
    @Override
    public boolean validate() {
        if (width<0||length<0) {
            return false;
        }
        return true;
    }
    @Override
    public String toString() {
        return "Rectangle [width=" + width + ", length=" + length + "]";
    }
    
}
class Triangle extends Shape {
    private double side1;
    private double side2;
    private double side3;
    
    public Triangle() {
        super();
        // TODO Auto-generated constructor stub
    }
    
    public Triangle(double side1, double side2, double side3) {
        super();
        this.side1 = side1;
        this.side2 = side2;
        this.side3 = side3;
    }
    

    public double getSide1() {
        return side1;
    }

    public void setSide1(double side1) {
        this.side1 = side1;
    }

    public double getSide2() {
        return side2;
    }

    public void setSide2(double side2) {
        this.side2 = side2;
    }

    public double getSide3() {
        return side3;
    }

    public void setSide3(double side3) {
        this.side3 = side3;
    }

    @Override
    public double getArea() {
        double p = (side1+side2+side3)/2.0;

        return Math.sqrt(p*(p-side1)*(p-side2)*(p-side3));
    }
    @Override
    public boolean validate() {
        if (side1<0||side2<0||side3<0) {
            return false;
        }
        if (side1+side2<=side3||side2+side3<=side1||side1+side3<=side2) {
            return false;
        }
        return true;
    }
    @Override
    public String toString() {
        return "Triangle [side1=" + side1 + ", side2=" + side2 + ", side3=" + side3 + "]";
    }
    
}

 该题为第六次作业中最繁琐的一题,但只是较为麻烦,有了之前设计类间关系的经验之后还是很好解决的。

使用PowerDesigner生成的类图如下:

该题算是第四次作业7-3题的升级版,Shape变为了抽象类,复用性和可读性大大提升,不再需要上次Shape中getArea()方法的return 0.0那种奇怪不知所云的代码了,直接顺理成章写成抽象方法,别的类可以大胆去继承然后Override重写成自己需求的样子,该题中对应为不同形状的面积计算方法,和之前题目的情况类似。validate()这个校验用的方法也写成了抽象的,在不同的形状类中再经重写变化成不同的校验标准,在主类输入数据之后调用不同类共有的validate()方法进行校验。从这两个抽象方法可以看出,抽象类/方法一个很大的作用就是把复用性很高的代码片段单独拎出来,给人一种直观的认识,知道“哦,这些东西标注了abstract,一看就是需要反复使用的”,也就是我们经常所说的可读性。

该题要求输入时“从键盘首先输入三个整型值(例如a b c),分别代表想要创建的Circle、Rectangle及Triangle对象的数量”,以及后续要求根据成员排序等操作,也就是说需要创建“对象数组”并进行排序,类似对应我们之前所学过的C语言中的结构体数组。该处为了尝试新的技术,也为了使用更便捷强大的方法,使用了ArrayList类这个非常重要的公用类,创建了一个动态数组,使用add方法添加元素,并使用泛型<>声明,使代码的安全性更佳。由于使用了List接口,最终用于排序的方法是Collections.sort,自定义排序方法,使用外部比较器Comparator,传参引用new Comparator<Shape>(),之后用Override重写compare()方法,实现自定义排序。输出则采用foreach遍历。

另外,基于该题的类图要求以及上次课堂练习的经验,在每个类中使用自动生成重写了toString这一公用方法(虽然该题最后输出不需要)。

 使用SourceMonitor分析代码如下:

 最大复杂度勉强尚可,略超标,原因估计出在创建对象数组时使用的循环,不知道是否有更好的方法。

 

 

7-6 实现图形接口及多态性 (30 分)
 

编写程序,使用接口及类实现多态性,类图结构如下所示:

类图.jpg

其中:

  • GetArea为一个接口,无属性,只有一个GetArea(求面积)的抽象方法;
  • Circle及Rectangle分别为圆类及矩形类,分别实现GetArea接口
  • 要求:在Main类的主方法中分别定义一个圆类对象及矩形类对象(其属性值由键盘输入),使用接口的引用分别调用圆类对象及矩形类对象的求面积的方法,直接输出两个图形的面积值。(要求只保留两位小数)

输入格式:

从键盘分别输入圆的半径值及矩形的宽、长的值,用空格分开。

输出格式:

  • 如果输入的圆的半径值及矩形的宽、长的值非法(≤0),则输出Wrong Format
  • 如果输入合法,则分别输出圆的面积和矩形的面积值(各占一行),保留两位小数。

PTA已提交的源码如下: 

 

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            double radius = input.nextDouble();
            double width = input.nextDouble();
            double length = input.nextDouble();
            if (radius<=0||width<=0||length<=0) {
                System.out.println("Wrong Format");
                System.exit(0);
            }
            Circle circle = new Circle(radius);
            Rectangle rectangle = new Rectangle(width,length);
            System.out.printf("%.2f\n",circle.getArea());
            System.out.printf("%.2f\n",rectangle.getArea());
        }
        finally {
            input.close();
        }
    }
}
class Circle implements GetArea{
    private double radius;

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public Circle(double radius) {
        super();
        this.radius = radius;
    }

    public Circle() {
        super();
        // TODO Auto-generated constructor stub
    }
    @Override
    public double getArea() {
        return Math.PI*radius*radius;
    }
}
class Rectangle implements GetArea {
    private double width;
    private double length;
    public double getWidth() {
        return width;
    }
    public void setWidth(double width) {
        this.width = width;
    }
    public double getLength() {
        return length;
    }
    public void setLength(double length) {
        this.length = length;
    }
    public Rectangle(double width, double length) {
        super();
        this.width = width;
        this.length = length;
    }
    public Rectangle() {
        super();
        // TODO Auto-generated constructor stub
    }
    @Override
    public double getArea() {
        return width*length;
    }
}
interface GetArea {
    public abstract double getArea();
}

该题实现了一个比较简单的接口结构,出现了类之间新的关系:实现关系(implements关键字),有别于之前父类子类/抽象类(extends关键字)的继承(泛化)关系。目前就本人所了解的一小部分知识,接口和抽象类这两者之间的区别直观上不太容易看出,都是一堆抽象方法放一起,然后去重写调用,但其实它们之间的区别还是有的。接口相当于是一大堆抽象方法的集合,而抽象类还算是类,两者本质上是有不同的;另外,一个子类只能有一个父类,但是可以存在多个接口。在新增方法时,接口每次新增时,实现接口的类也要新增对应的方法,而抽象类新增非抽象方法时,子类可以不用新增方法。据说抽象方法比接口的代码运行速度还要快一些。

生成的类图如下,和题目要求一致:

该代码中Circle类及Rectangle类分别包含各自属性(半径,长宽),分别实现了GetArea接口用来计算各自的面积,GetArea接口只包含一个抽象方法getArea()。接口不能直接被实例化,所以代码中引用了接口的实现类,将其实例化并传值,分别调用重写后的方法求出了面积。

经过本人一时兴起的尝试,接口可以直接被引用,然后实例化出实现类的对象,最终实现效果是相同的,上代码:

import java.util.*;
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        try {
            double radius = input.nextDouble();
            double width = input.nextDouble();
            double length = input.nextDouble();
            if (radius<=0||width<=0||length<=0) {
                System.out.println("Wrong Format");
                System.exit(0);
            }
            GetArea getarea1 = new Circle(radius);
            GetArea getarea2 = new Rectangle(width,length);
            System.out.printf("%.2f\n",getarea1.getArea());
            System.out.printf("%.2f\n",getarea2.getArea());
        }
        finally {
            input.close();
        }
    }
}
class Circle implements GetArea{
    private double radius;

    public double getRadius() {
        return radius;
    }

    public void setRadius(double radius) {
        this.radius = radius;
    }

    public Circle(double radius) {
        super();
        this.radius = radius;
    }

    public Circle() {
        super();
        // TODO Auto-generated constructor stub
    }
    @Override
    public double getArea() {
        return Math.PI*radius*radius;
    }
}
class Rectangle implements GetArea {
    private double width;
    private double length;
    public double getWidth() {
        return width;
    }
    public void setWidth(double width) {
        this.width = width;
    }
    public double getLength() {
        return length;
    }
    public void setLength(double length) {
        this.length = length;
    }
    public Rectangle(double width, double length) {
        super();
        this.width = width;
        this.length = length;
    }
    public Rectangle() {
        super();
        // TODO Auto-generated constructor stub
    }
    @Override
    public double getArea() {
        return width*length;
    }
}
interface GetArea {
    public abstract double getArea();
}

貌似和继承关系中的父类引用指向子类对象是一个原理?

SourceMonitor分析如下:

最大复杂度很小,程序本身算法也很简单。这也印证了我之前的猜想,最大复杂度和最终解决问题时的算法有很大关系,该题几乎不存在循环和选择,一路顺着走下来,当然没什么复杂度。

  

(4)关于正则表达式的使用

第四第五次作业对正则的要求和第三次最后一题一样,属于较高的级别,基本都是一个套路:用正则配合字符串(String类)的处理方法(经常用的比如split,equal这种)去对输入的数据进行格式校验、匹配并且根据格式去分割字符串,把有用的数据提出来再做别的事情。而第六次题目的正则相较上次第三次作业的要求有所减轻。比如第四次作业7-1题的正则是用在分割输入的数据,进行水文的格式校验,之后再取出数字去进行别的计算来完成题目的需求;又比如第五次作业7-4题的正则也是用在匹配和搭配字符串方法处理代码中的各种标点以及[],{},()等等这种符号,将其剔除后留下关键字,再去进行关键字的计数。第六次作业的几个小题目的思路相对而言就容易得多了,写好正则以后直接对输入的字符串用.matches匹配就够了。

关于正则的写法,有几个可能容易遗漏但可以简化思路的点,一个是[^]表示排除,比如[^ab]表示除a,b之外任一字符,^放最前面指定起始字符,$放最后面指定终止字符,.匹配任何字符(除\n之外),\d匹配单个数字,\D匹配单个非数字,\w匹配单个数字+大小写字母,\W就又是反过来非数字+大小写字母。还有几个非常容易遗忘的点,*放后面表示指定字符重复0次或n次(可出现可不出现,出现次数不确定) ,?放后面表示指定字符重复0次或1次(只可能是出现一次或者不出现),+放后面表示指定字符重复1次或n次(出现至少一次),对于其它次数的要求则使用{}完成,{x}表示限定出现x次,{x,}表示>=x次,{x,y}则是区间n次(x<=n<=y)(就和数学里定义域值域的那个小括号用法差不多)。

 

三、踩坑心得

 (1)第四次作业中

 首先就是7-2题,由于题目要求类之间使用的是“链式”结构,不同类之间有一条锁链关系,用getDay/getMonth/getYear方法自上而下一条龙调用,因此这个链不能忘记,方向也不能弄反。根据我的观察,很多人(包括我自己也有过)都容易在这种地方犯错。

 如果像我一样复用自己写过的算法,则要记得每算完一步都往类中传一次值,避免出现奇怪的错误。

并且maxDay[]数组别忘记写了。要么写在类属性中公用,要么每个方法中都写(不推荐,该题是由于类图要求)。

在数组下标进行操作时,一定要考虑清楚什么时候会越界,用if分支处理特殊情况。分类讨论要思路清晰不遗漏,正负号要想清楚,不然都会有各种奇怪的错误导致过不了测试点。

7-3题踩过的坑属于完全可以规避的技术问题(用父类引用指向子类对象等),没什么太大意义,所以不细讲,但是值得注意的是定义对象引用的时候要考虑清楚,什么时候用父类,什么时候用子类。

 

 (2)第五次作业中

 首先是7-1题,找出最长的单词,(输入格式为单行形式,单词之间使用空格分割。)看到这种描述就建议别想着别的方法,乖乖用split...我已经尝试过了各种手动方法,把单词每输入一个处理一次,这种在C语言里面用得比较普遍的办法在用Scanner输入的时候(再加上还得算每个单词长度),反而是显得异常麻烦,最简单强大实用的处理方法就是split按空格分隔存进字符串数组,配合一次循环遍历就能解决。

7-5题中有几个很大的坑点,如图:

如果你像我一样用这种思路:getDaysofDates方法返回都的是0001年1月1日距离参数中日期的天数,两个日期分别求一遍距离0001年1月1日的相差天数,再相减得到两个日期间相差天数,那么千万别忘了进行这样一个类似于求绝对值的操作。

另外,在getNextNDays方法中:

 

该处会有一个int32最大值的测试点不好过,我使用了一种巧方法绕过去,当检测到输入数据为整型最大值时,把+1之后那部分溢出的数据拎出来单独算,虽然不是最好的办法,但重点在于int32最大值这个测试点是一个很大的坑点,不管用什么方法去绕,都值得注意。

 

 

(3)第六次作业中

 7-2题字符排序,要记得可以直接用Arrays的sort方法去排序字符(注意字符是char类型),可以省大量精力,前提是要记得把String转成字符数组,和C语言一样理解即可。

7-3题验证码校验建议用现成的\w一口气包含数字和大小写字母,省时省力。

 7-4题学号校验,如果和我一样用|分隔表示”或“的意思来分类讨论,记得|的前后都必须是完整的正则表达式,不能只是表达式的一部分,那样是匹配不到的。

 

四、改进建议

 首先是第四次作业7-2题,求下n天,前n天和相差天数的方法都是改自之前自己写的一个计算日期的方法,改得比较生硬,都是先提出数据,然后算一步用Setter放回去一步,没有用到题目给的其它几个类(自增自减还有比较日期等等),甚至于maxDay[]数组和判断闰年给二月增加一天的模块都得反复零散出现在好几个方法里,算法不够简洁明了,复用性和内聚性都不够好,值得修改,应该在设计的时候就将能合在一起的部分单独放在一起去复用。对于基本的日期算法,也是采用了下标直接运算以及反复颠倒正负数的方法,容易发生数组下标越界以及正负号弄反弄错的情况,在写代码时容易出更多错误,难以调试,容易产生隐患,值得改进为更简洁的算法。(第五次作业7-5题也是同理)

第四次作业7-3题,之前提到过的,运用多态不熟练,方法覆盖使用的(子类)对象引用了本类(子类)实例,仅体现了编译时多态,未体现运行时多态(动态绑定),不存在父类引用指向子类对象。这也导致之前多次踩到一些坑,程序无法正常传入传出数据,原因应该就出在父类对象访问不了子类对象这一块,加上当时也不熟悉super.可以访问父类方法,导致写程序时对象之间的访问出现了混乱。该处目前的代码也是修修补补,很不统一,为了过测试点既存在用Setter传值又存在用带参构造方法传值,而且到通过测试点之后也没有完全发现之前各种引用混乱的问题是哪出来的,如果让父类引用指向子类对象应该可以解决。

第五次作业7-4题,则可以学习他人的较优质代码,把List框架换成Map等,提升查找效率。

第五次作业7-5题,如图:

因为看到了测试点会测int32最大值,最大值算到+1的时候肯定会数据溢出,又懒得改已经写好的int数据类型,所以使用这种巧方法刚好把最大值那个数给成功绕过去了,但是这并不是最好的办法,在繁琐的同时还增加了分支判断,增大了程序复杂度。最好还是换个范围更大的数据类型,或者尝试别的办法(比如直接改算法不给+1的机会?或者还有更好的绕过最大值的方法)。

 第六次作业7-4题学号校验,我发现只用正则的|符号只能是在该符号前后各写一个完整的正则,达成“或”的效果,进行分类的选择,而不能做到对表达式中的一部分进行选择,这就导致了表达式的繁琐:

String reg = "20201[1-7][0-3][1-9]|202061[0-3][1-9]|20207[1-3][0-3][1-9]|20208[12][0-3][1-9]|20201[1-7]40|20206140|20207[1-3]40|20208[12]40";

虽然题目要求确实挺多,但正则也的确写得太长太繁琐,值得去探寻一下有没有别的办法改成短小精悍的(目前我怀疑用分组括号可能可以解决一部分问题?),就算没有找到更好的办法也能在其中获得成长。

 最后,这几次作业所有的代码在检测复杂度的时候质量表现都不太好,值得去改进基本的算法结构(比如日期类的那些题),以及去学习把if else的结构用一些新技术等效替代成别的。

 

五、总结

        这几次作业中,主要的新东西集中在类之间关系的继续拓展延伸,即聚合(和之前最基础的关联基本一样,但这几次作业中对求解方法的难度和复杂度增加),继承与多态(具体表现在子类,父类,抽象类,接口,Override等)的设计思路上,在反复迭代式的运用中去巩固旧的套路和发现新的技术知识,逐渐深入面向对象思想的核心部分。另外一些新技术,比如集合框架接口(List,Set,Map)及一些通用类(Collections,Arrays)还有泛型等的简单使用也有涉及,不断接触新知识使得刚踏入编程世界不久的我们能够快速汲取养分,灵活运用这些更深一步的知识去解决更加复杂的问题,进而进一步去提高程序设计思想的水平,达成良性循环。

       对于新知识,本人自我感觉掌握得还算可以,就是在多态的深入运用方面尚显稚嫩,代码一看过去就“莫得灵魂”,有一种刻意为之的感觉,没有老手那种对类之间关系设计游刃有余的感觉,在引用和实例化方面时不时会犯一些自己一时也搞不明白的奇怪错误,这是急需加强的。对于一些之前涉及过的字符串处理、正则表达式等等技术,这几次作业也有反复锻炼,可以看出我的掌握仍然有很多欠缺,虽然在基础的写正则和字符串简单处理方面是基本不存在问题了,但由于之前的时间安排问题,没有能够在DDL内完成那几个较为庞大的字符串处理任务,在灵活运用和构思复杂问题解决思路的方面不足,给今后的学习埋下了隐患,务必要找时间补回来。对一些基础的排序算法,我较久未用便有所遗忘,也应该去复习,自己多写几遍,将其拾起。对于集合框架,之后的运用肯定会越来越多,我认为绝不能停留在现在这种表面肤浅的运用程度上,一定要通过自学继续去深入探究,把原理弄清楚,再把更多更灵活的运用方式都掌握。对于老师提过的重构if else代码减少复杂度的技术,也应该抓紧去学习。

       鉴于本人是初学者,本文中肯定存在相当的疏漏,希望阅读过文章的各位,无论是助教还是老师还是不知名的朋友,能够予以一定程度的包容并批评指正,感谢各位。

posted @ 2021-05-02 23:13  onlyabsolutely  阅读(66)  评论(0编辑  收藏  举报