一、前言
前三次作业的题量在整个OOP课程当中属于题量较小的情况,但对于初次接触面向对象的我来说,这些题也花费了不少的时间来完成。难度上来说,这三套题集都有明显的难度梯度划分,从前往后的难度是从易到难,在每个题目集内部难度分布也是如此,符合课程学习的一般规律。三次题目集整体难度不高,主要是为了让我们快速了解和熟悉一些基本的java语法,以及一些常用的类。现对前三次题目集的知识部分进行回顾,将涉及到的知识点尽可能全面地罗列出来。
(一)知识点:
-
- 代码风格需遵守阿里巴巴Java开发手册的编码规范,例如:
if(A<20&&A>0) { cost=12+(A-1)*2; } // 不符合标准的格式:运算符之间无空格、if无空格且其后第一个花括号单独占一行、变量名太随意,无法做到 “见名知意”、夸张的缩进
if (weight < 20 && weight > 0){ cost = 12 + (weight - 1 ) * 2; } // 符合标准的格式
- 代码风格需遵守阿里巴巴Java开发手册的编码规范,例如:
-
- Java导入工具包的方式及常见方法所属的工具包,例如:Scanner类需要用 import java.util.Scanner; 导入
- 对象的概念
- 类的概念及如何自定义类并实现一些简单功能,例如:
class Circle { private double radius; public double getRadius() { return this.radius; } public void setRadius(double radius) { this.radius = radius; } public double getS() { return (Math.PI) * this.radius * this.radius; } }
// 以上代码创建了一个圆形类,具有一个私有的double类型属性半径,在类外可通过公共的方法获取半径、圆的面积、修改半径
- List集合下的字符串类StringBuffer的常见方法(后面的课程中介绍了StringBuilder和StringBuffer的区别,限于作者当时刚开始学习Java,首次使用字符串时用的是StringBuffer),例如截取、比较等等。字符串这部分内容和C语言有不小的区别
- 泛型的概念
- List集合的迭代器遍历方式
二、设计与分析
-
- 题目集3的第三题和第四题的综合性在所有题目中最强,基本涵盖了前两套题目集中的所有知识点,因此这里作者只分析这两题
(一)题目集3,第3题:
1. 原题放送:
2. 题目分析:
这道题由于给出了我们Date的类图,因此不需要我们自己设计,只需要根据类图将类实现即可,难度不高,重点在于正确识读类图
需要注意的是这个类有两种构造方法,可进行重载,一种构造方法是默认的,另一种构造方法是传入year,month,day进行初始化
3. 代码设计:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 import java.util.*; 2 3 public class Main{ 4 public static void main(String args[]){ 5 Scanner in = new Scanner(System.in); 6 Date date = new Date(in.nextInt(), in.nextInt(), in.nextInt()); 7 if(date.checkInputValidity()){ 8 date.getNextDay(); 9 if(date.checkInputValidity()){ 10 System.out.printf("Next day is:%d-%d-%d\n",date.year,date.month,date.day); 11 } 12 } 13 } 14 } 15 16 class Date { 17 int year; 18 int month; 19 int day; 20 int[] mon_maxnum = new int[]{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 21 22 public Date() { 23 } 24 25 public Date(int year, int month, int day) { 26 this.year = year; 27 this.month = month; 28 this.day = day; 29 } 30 31 public int getYear() { 32 return year; 33 } 34 35 public int getMonth() { 36 return month; 37 } 38 39 public int getDay() { 40 return day; 41 } 42 43 public void setYear(int year) { 44 this.year = year; 45 } 46 47 public void setMonth(int month) { 48 this.month = month; 49 } 50 51 public void setDay(int day) { 52 this.day = day; 53 } 54 55 public boolean isLeapYear(int year) { 56 boolean flag = false; 57 if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) { 58 flag = true; 59 } 60 return flag; 61 } 62 63 public boolean checkInputValidity() { 64 boolean flag = true; 65 if (this.year < 1900 || this.year > 2000 || this.month < 1 || this.month > 12) { 66 flag = false; 67 } else if (isLeapYear(this.year)) {// 将闰年和非闰年情况分类讨论 68 if (this.month != 2) { 69 if (this.day < 0 || this.day > mon_maxnum[this.month]) { 70 flag = false; 71 } 72 } else { 73 if (this.day > 29 || this.day < 1) { 74 flag = false; 75 } 76 } 77 } else { 78 if (this.day < 1 || this.day > mon_maxnum[this.month]) { 79 flag = false; 80 } 81 } 82 if(flag == false){ 83 System.out.print("Date Format is Wrong\n"); 84 } 85 return flag; 86 } 87 88 public void getNextDay() { 89 90 if (this.month == 12 && this.day == 31) {// 最后一天的下一天 91 this.year++; 92 this.month = 1; 93 this.day = 1; 94 } else if (!isLeapYear(this.year)) {// 非最后一天的平年的下一天 95 if (this.day + 1 > mon_maxnum[this.month]) { 96 day = 1; 97 this.month++; 98 } else { 99 this.day++; 100 } 101 } else if (isLeapYear(this.year)) {// 非最后一天闰年的下一天 102 if (this.month == 2) { 103 if (this.day + 1 > 29) { 104 day = 1; 105 this.month++; 106 } else { 107 this.day++; 108 } 109 } else { 110 if (this.day + 1 > mon_maxnum[this.month]) { 111 day = 1; 112 this.month++; 113 } else { 114 this.day++; 115 } 116 } 117 } 118 } 119 }
-
- 在实现 getNextDay() 这个方法时,调用了isLeapYear() 方法,根据是否为闰年,把日期的进位情况分类讨论
三、 踩坑心得
这是作者第一次提交的结果
从测试点不难看出,平台进行了多个无效值测试,其中有一个无效值是我在设计checkInputValidity() 方法时没有考虑到的,通过多次尝试,摸索出了该非法边界值。
测试用例:
1900 1 0
输出结果:
Next day is:1900-1-1
显然,1900年1月0日是一个错误的输入。由于该类内部方法采用的是模块化设计,我们很快就能根据各个方法的职责定位到有问题的代码,即checkInputValidity() 方法,以下是作者最初的checkInputValidity() 方法。
1 public boolean checkInputValidity() {
2 boolean flag = true;
3 if (this.year < 1900 || this.year > 2000 || this.month < 1 || this.month > 12) {
4 flag = false;
5 } else if (isLeapYear(this.year)) {
6 if (this.month != 2) {
7 if (this.day < 0 || this.day > mon_maxnum[this.month]) {
8 flag = false;
9 }
10 } else {
11 if (this.day > 29 || this.day < 1) {
12 flag = false;
13 }
14 }
15 } else {
16 if (this.day < 0 || this.day > mon_maxnum[this.month]) {
17 flag = false;
18 }
19 }
20 if(flag == false){
21 System.out.print("Date Format is Wrong\n");
22 }
23 return flag;
24 }
根据上面的测试用例,推断是第7行和第16行 if (this.day < 0 || this.day > mon_maxnum[this.month]) 出现问题,我们仔细看一下这个逻辑,一个满足 0 <= this.day <= mon_maxnum[this.month] 的 day 的值被认为是合法的,则day = 0也合法。这不符合日期的逻辑,因此只需要修改为 if (this.day < 1 || this.day > mon_maxnum[this.month]) 即可
四、改进建议
(一)遵循SRP原则
以下面checkInputValidity() 方法为例:
1 public boolean checkInputValidity() {
2 boolean flag = true;
3 if (this.year < 1900 || this.year > 2000 || this.month < 1 || this.month > 12) {
4 flag = false;
5 } else if (isLeapYear(this.year)) {
6 if (this.month != 2) {
7 if (this.day < 0 || this.day > mon_maxnum[this.month]) {
8 flag = false;
9 }
10 } else {
11 if (this.day > 29 || this.day < 1) {
12 flag = false;
13 }
14 }
15 } else {
16 if (this.day < 1 || this.day > mon_maxnum[this.month]) {
17 flag = false;
18 }
19 }
20 if(flag == false){
21 System.out.print("Date Format is Wrong\n");
22 }
23 return flag;
24 }
第20~22行的输出放在这个方法里不太符合单一职责原则,“输出”的职责不应该放在 “判断输入是否合法” 的方法中,
符合SRP原则的编码方式应该是在主函数中根据checkInputValidity() 的返回值决定是否执行 System.out.print("Date Format is Wrong\n"); 。