面向对象程序设计题目集1~3总结blog
一、前言
首次在博客园上发布面向对象程序设计(Java)的题目集总结blog。 这三次作业总的来说算是课程的一个入门,前两次主要是对Java语言语法知识的考察,第三次作业才开始体现了面向对象程序设计的内容。
- 关于知识点:
前两次作业主要是运用Java的基本语法知识,例如:选择、循环、数组、方法等内容。语法知识基本与C语言一致,除了一些表达方式不同,还有比如输入输出语句还有初始化数组等一些语句不同,因此写起来也更容易适应。而到了第三次作业中就是设计类,在类中创造方法,开始展现类的封装性,与之前所学便有了极大差异。
- 关于题量:
每次作业时间均是一星期。题目数量不断减少,但是难度也在逐渐增加,所以花在写PTA上的时间总的来说还是不少,甚至会增加。后面题目集三都需要设计类,就需要对程序进行设计,不然会很混乱。前期其他课程的作业量也比较多,这种题量较为适中。
- 关于难度:
我接触Java这门语言时间还不是很长,对于面向对象这一概念目前也只是了解到皮毛,许多知识还得是通过书本、自学获取,也并没能熟练掌握,写这种题目起初归是有些许的困难。特别是后面题目,代码量不断增加,设计出类和方法就开始有点晕。面对一道题的错误就要改不少时间,有的时候改了一个错误又出现了其他错误就会手足无措,不知道下一步应该干些什么。
二、设计与分析
此部分只对题目集中的部分题目进行分析。
- 第三次题目7-2
类图:
代码实现:
1 import java.time.LocalDate; 2 import java.util.Scanner; 3 public class Main { 4 public static void main(String[] args){ 5 Scanner in = new Scanner(System.in); 6 Account a = new Account(); 7 a.setId(in.nextInt()); 8 a.setBalance(in.nextDouble()); 9 a.setAnnualInterestRate(in.nextDouble()); 10 a.withDraw(in.nextDouble()); 11 a.deposit(in.nextDouble()); 12 System.out.printf("The Account'balance:%.2f\n", a.getBalance()); 13 System.out.printf("The Monthly interest:%.2f\n", a.getMonthlyInterestRate()); 14 System.out.println("The Account'dateCreated:" + a.getDataCreated()); 15 } 16 } 17 class Account{ 18 private int id; 19 private double balance; 20 private double annualInterestRate; 21 private final LocalDate dataCreated = LocalDate.of(2020, 7, 31); 22 public double getId(){ 23 return id; 24 } 25 public void setId(int id){ 26 this.id = id; 27 } 28 public double getBalance(){ 29 return balance; 30 } 31 public void setBalance(double balance){ 32 this.balance = balance; 33 } 34 public double getAnnualInterestRate(){ 35 return annualInterestRate; 36 } 37 public void setAnnualInterestRate(double annualInterestRate){ 38 this.annualInterestRate = annualInterestRate; 39 } 40 public LocalDate getDataCreated(){ 41 return dataCreated; 42 } 43 public double getMonthlyInterestRate(){ 44 return balance * (annualInterestRate / 1200); 45 } 46 public void withDraw(double withdraw){ 47 if(withdraw < 0 || withdraw > balance){ 48 System.out.println("WithDraw Amount Wrong"); 49 }else { 50 balance -= withdraw; 51 } 52 } 53 public void deposit(double deposit){ 54 if (deposit > 20000 || deposit < 0){ 55 System.out.println("Deposit Amount Wrong"); 56 }else { 57 balance += deposit; 58 } 59 } 60 }
SourceMonitor上代码质量分析结果:
分析:
从图上不难看出大部分的数值还是在正常范围内,复杂性较低,函数的深度也不高,代码质量还有较大的进步空间。这道题设置账户类,完成账户金钱的存取、账号内的金额以及利率计算。在方法 getMonthlyInterestRate 中进行利率计算,withdraw中判断是否能够取钱,deposit里判断能否存储钱。这道题相对简单,进行的运算也不是很复杂,不过在格式上可能还存在着不规范,需要在后续作业中注意。
7-3和7-4都是设计日期类,但是功能需求有所不同,也是本次blog重点进行分析的两道题目
- 第三次题目7-3
类图:
代码实现:
1 import java.util.Scanner; 2 3 public class Main { 4 public static void main(String[] args) { 5 Scanner in = new Scanner(System.in); 6 Date date = new Date(); 7 date.setYear(in.nextInt()); 8 date.setMonth(in.nextInt()); 9 date.setDay(in.nextInt()); 10 if (!date.checkInputValidity()){ 11 System.out.println("Date Format is Wrong"); 12 }else { 13 date.getNextDate(date.getYear(), date.getMonth(), date.getDay()); 14 } 15 } 16 } 17 18 class Date{ 19 private int year; 20 private int month; 21 private int day; 22 int[] mon_maxnum = new int[] {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 23 public Date() { 24 this.year = 0; 25 this.month = 0; 26 this.day = 0; 27 } 28 public Date(int year, int month,int day) { 29 this.year = year; 30 this.month = month; 31 this.day = day; 32 } 33 public int getYear() { 34 return year; 35 } 36 public void setYear(int year) { 37 this.year = year; 38 } 39 public int getMonth() { 40 return month; 41 } 42 public void setMonth(int month) { 43 this.month = month; 44 } 45 public int getDay() { 46 return day; 47 } 48 public void setDay(int day) { 49 this.day = day; 50 } 51 public boolean isLeapYear(int year) { 52 boolean isLeapyear; 53 isLeapyear = ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); 54 return isLeapyear; 55 } 56 public boolean checkInputValidity() { 57 boolean checkInputValidity; 58 Date d = new Date(); 59 if (d.isLeapYear(year) && month == 2 ) { 60 mon_maxnum[2] = 29; 61 } 62 checkInputValidity = !(year < 1900 || year > 2000 || month < 1 || month > 12 || day > mon_maxnum[month] || day < 1); 63 return checkInputValidity; 64 } 65 public void getNextDate(int year, int month, int day) { 66 if (checkInputValidity()) { 67 boolean month1 = (month == 12); 68 boolean days = (day == mon_maxnum[month]); 69 if (!month1 && days) { 70 month += 1; 71 day = 1; 72 } else if(month1 && days) { 73 year += 1; 74 month = 1; 75 day = 1; 76 } else { 77 day += 1; 78 } 79 } 80 System.out.println("Next day is:"+year+"-"+month+"-"+day); 81 } 82 }
SourceMonitor上代码质量分析结果:
分析:
跟据上面数据可以看出,平均复杂度勉强达到要求,平均每个方法中的语句还是较少且未达到绿色标准区域,但总体与上题相比较有所提升。代码实现的功能是输入一个时期并输出一个日期的下一天,得注意的地方除了输入日期不在题目范围内、不存在的日期,就是闰年和平年2月份总天数的不同以及12月31日下一天是新的一年1月1日,其余的就是直接把日期加一即可。功能比较简单,只需判断月份和天数是否达到上述注意点即可。
- 第三次题目7-4
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner input = new Scanner(System.in); int year = 0; int month = 0; int day = 0; int choice = input.nextInt(); if (choice == 1) { // test getNextNDays method int m = 0; year = Integer.parseInt(input.next()); month = Integer.parseInt(input.next()); day = Integer.parseInt(input.next()); DateUtil date = new DateUtil(year, month, day); if (!date.checkInputValidity()) { System.out.println("Wrong Format"); System.exit(0); } m = input.nextInt(); if (m < 0) { System.out.println("Wrong Format"); System.exit(0); } System.out.print(date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " next " + m + " days is:"); System.out.println(date.getNextNDays(m).showDate()); } else if (choice == 2) { // test getPreviousNDays method int n = 0; year = Integer.parseInt(input.next()); month = Integer.parseInt(input.next()); day = Integer.parseInt(input.next()); DateUtil date = new DateUtil(year, month, day); if (!date.checkInputValidity()) { System.out.println("Wrong Format"); System.exit(0); } n = input.nextInt(); if (n < 0) { System.out.println("Wrong Format"); System.exit(0); } System.out.print( date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " previous " + n + " days is:"); System.out.println(date.getPreviousNDays(n).showDate()); } else if (choice == 3) { //test getDaysofDates method year = Integer.parseInt(input.next()); month = Integer.parseInt(input.next()); day = Integer.parseInt(input.next()); int anotherYear = Integer.parseInt(input.next()); int anotherMonth = Integer.parseInt(input.next()); int anotherDay = Integer.parseInt(input.next()); DateUtil fromDate = new DateUtil(year, month, day); DateUtil toDate = new DateUtil(anotherYear, anotherMonth, anotherDay); if (fromDate.checkInputValidity() && toDate.checkInputValidity()) { System.out.println("The days between " + fromDate.showDate() + " and " + toDate.showDate() + " are:" + fromDate.getDaysofDates(toDate)); } else { System.out.println("Wrong Format"); System.exit(0); } } else{ System.out.println("Wrong Format"); System.exit(0); } } }
类图:
代码实现:
import java.util.Scanner; public class Main { public static void main(String[] args) { Scanner input = new Scanner(System.in); int year = 0; int month = 0; int day = 0; int choice = input.nextInt(); if (choice == 1) { // test getNextNDays method int m = 0; year = Integer.parseInt(input.next()); month = Integer.parseInt(input.next()); day = Integer.parseInt(input.next()); DateUtil date = new DateUtil(year, month, day); if (!date.checkInputValidity()) { System.out.println("Wrong Format"); System.exit(0); } m = input.nextInt(); if (m < 0) { System.out.println("Wrong Format"); System.exit(0); } System.out.print(date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " next " + m + " days is:"); System.out.println(date.getNextNDays(m).showDate()); } else if (choice == 2) { // test getPreviousNDays method int n = 0; year = Integer.parseInt(input.next()); month = Integer.parseInt(input.next()); day = Integer.parseInt(input.next()); DateUtil date = new DateUtil(year, month, day); if (!date.checkInputValidity()) { System.out.println("Wrong Format"); System.exit(0); } n = input.nextInt(); if (n < 0) { System.out.println("Wrong Format"); System.exit(0); } System.out.print( date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " previous " + n + " days is:"); System.out.println(date.getPreviousNDays(n).showDate()); } else if (choice == 3) { //test getDaysofDates method year = Integer.parseInt(input.next()); month = Integer.parseInt(input.next()); day = Integer.parseInt(input.next()); int anotherYear = Integer.parseInt(input.next()); int anotherMonth = Integer.parseInt(input.next()); int anotherDay = Integer.parseInt(input.next()); DateUtil fromDate = new DateUtil(year, month, day); DateUtil toDate = new DateUtil(anotherYear, anotherMonth, anotherDay); if (fromDate.checkInputValidity() && toDate.checkInputValidity()) { System.out.println("The days between " + fromDate.showDate() + " and " + toDate.showDate() + " are:" + fromDate.getDaysofDates(toDate)); } else { System.out.println("Wrong Format"); System.exit(0); } } else{ System.out.println("Wrong Format"); System.exit(0); } } } class DateUtil { private int year; private int month; private int day; int[] mon_maxnum = new int[] {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; public DateUtil() { this.year = 0; this.month = 0; this.day = 0; } public DateUtil(int year, int month,int day) { this.year = year; this.month = month; this.day = day; } public int getYear() { return year; } public void setYear(int year) { this.year = year; } public int getMonth() { return month; } public void setMonth(int month) { this.month = month; } public int getDay() { return day; } public void setDay(int day) { this.day = day; } public boolean checkInputValidity() { boolean checkInputValidity; DateUtil d = new DateUtil(); if (d.isLeapYear(year) && month == 2 ) { mon_maxnum[2] = 29; } checkInputValidity = !(this.year < 1820 || this.year > 2020 || this.month < 1 || this.month > 12 || this.day > mon_maxnum[this.month] || this.day < 1); return checkInputValidity; } public boolean isLeapYear(int year) { boolean isLeapyear; isLeapyear = ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0); return isLeapyear; } public DateUtil getNextNDays(int n) { if(isLeapYear(this.year) && this.month == 2){ mon_maxnum[2] = 29; } while (n > 365) { if (isLeapYear(this.year) && this.month <= 2) { if (this.month == 2 && this.day == mon_maxnum[2]) { this.year += 1; this.month = 3; this.day = 1; n -= 366; } } else if (isLeapYear(this.year + 1) && this.month > 2) { this.year += 1; n -= 366; } else { this.year += 1; n -= 365; } } for (int x = 0; x < n; x++) { this.day++; if (this.day > mon_maxnum[this.month]) { this.day = 1; this.month++; if (this.month > 12) { this.year++; this.month = 1; } } } return new DateUtil(this.year, this.month, this.day); } public DateUtil getPreviousNDays(int n){ if(this.isLeapYear(this.year) && this.month == 2){ mon_maxnum[2] = 29; } while (n > 365) { if (isLeapYear(year) && month >= 3) { if (this.month == 3 && this.day == 1) { this.month = 2; this.day = 29; this.year -= 1; n -= 366; } } else if (isLeapYear(this.year - 1) && this.month < 3) { this.year -= 1; n -= 366; } else { this.year -= 1; n -= 365; } } for (int y = 0; y < n; y++) { this.day--; if (this.day < 1) { this.month--; if (this.month == 0) { this.year--; this.month = 12; } this.day = mon_maxnum[this.month]; } } return new DateUtil(this.year, this.month, this.day); } public boolean compareDates(DateUtil date) { return (this.year < date.year || this.month < date.month || this.day < date.day); } public boolean equalTwoDates(DateUtil date) { return (this.year == date.year && this.month == date.month && this.day == date.day); } public int getDaysofDates(DateUtil date){ int difference = 0; if (equalTwoDates(date)) { difference = 0; } if (compareDates(date)) { if (date.year > this.year) { for (int i = this.year + 1; i < date.year; i++) { if (isLeapYear(i)) { difference += 366; } else { difference += 365; } } for (int j = this.month + 1; j <= 12; j++) { difference += mon_maxnum[j]; } for (int k = 1; k <= date.month; k++) { difference += mon_maxnum[k]; } } else { for (int l = this.month; l < date.month; l++) { difference += mon_maxnum[l]; } } difference += date.day - this.day; } return difference; } public String showDate(){ return this.year + "-" + this.month +"-" + this.day; } }
SourceMonitor上代码质量分析结果:
分析:
这次的代码许多方面并没能达到标准范围之内,复杂度很高。此题是通过设计日期类实现三个功能:通过输入数字1、2、3分别实现计算日期的下n天、前n天以及两个日期的日期差。这道题因为不确定n的具体范围,有可能不到一个月,也有可能大于365,就使得这题变得比较困难。功能里不仅需要往后求下n天,还要往前计算前n天。代码一长,写代码的时候很容易疏忽一些小的点。在写完之后,我自己在IDEA上输入数据测试,也是出现了很多情况:例如答案错误,离正确的答案相差几天,甚至改完之后发现这组数据能够达到正确答案,但是换组数据又出现错误,可能出现的错误比之前的还离谱,这个过程就十分折磨。这题比原先的题复杂了不少,即使在看到题目之后做好了设计,我也未能拿到满分,有几个测试点并没有通过,也不知道测试点该如何通过。
例如上面展示的两个测试点。
三、踩坑心得
- 在第一次和第二次的题目集中,题目作者限制了输出的数据必须是float型,但是没有在题目中提及。
下图是提交的源码后显示的测试点:
没通过测试点的代码:
import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner in = new Scanner(System.in); int times = in.nextInt(); int price = in.nextInt(); int assess = in.nextInt(); double area = in.nextDouble(); double tax1 = 0,tax2,cost1,cost2; if(times == 1 && area <= 90){ tax1 = price * 0.01 * 10000; } else if(times == 1 && area > 90 && area <=144){ tax1 = price * 0.015 * 10000; } else if(times != 1 || area > 144){ tax1 = price * 0.03 * 10000; } tax2 = price * 0.0005 * 10000; cost1 = 3 * area; cost2 = 1.36 * area; System.out.print(tax1 +" "+ tax2 +" "+ cost1 +" "+ cost2); } }
写的时候自我感觉良好,用了题目的测试用例也都通过了,并没有出现什么问题。
通过测试点的代码:
import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner in = new Scanner(System.in); int times = in.nextInt(); int price = in.nextInt(); int assess = in.nextInt(); double area = in.nextDouble(); double tax1 = 0,tax2,cost1,cost2; if(times == 1 && area <= 90){ tax1 = assess * 0.01 * 10000; } else if(times == 1 && area > 90 && area <=144){ tax1 = assess * 0.015 * 10000; } else if(times > 1 || area > 144){ tax1 = assess * 0.03 * 10000; } tax2 = price * 0.0005 * 10000; cost1 = 3 * area; cost2 = 1.36 * area; System.out.print((float)tax1 +" "+ (float)tax2 +" "+ (float)cost1 +" "+ (float)cost2);//设置了输出的数据类型为float类型 } }
这个真的是一个很大的坑啊,当时写的时候看代码看了很久也不知道有什么错误,还是别人告诉我限制了数据类型。测试点和题目里没有提醒,直接显示答案错误,还得猜测试点的测试内容。
- 接下来的坑就是关于PTA的了。PTA有些题目通常会设置一些内存和时间限制,可能在其他的编译器上(如Eclipse和Intellij IDEA)运行是正常的,但是在PTA上会显示内存超限、运行超时等问题,当然运行Java代码自身需要的内存和时间就不少。但是,过段时间再次提交的时候却又能通过这个测试点。PTA还是这么令人捉摸不透。
附上源码:
import java.util.Arrays; import java.util.Scanner; class Main { public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int[] a = new int[n]; boolean flag = true; for (int i = 0; i < n; i++) { a[i] = in.nextInt(); } Arrays.sort(a); for (int i = 0; i < n - 1; i++) { if (a[i] == a[i + 1]) { flag = false; break; } } if (flag) { System.out.println("NO"); } else { System.out.println("YES"); } } }
过了一段时间又再次提交,没有对代码进行改动,成功通过测试点。
这种方式也不是每次都行得通,但至少这一次成功通过了测试点。这一次测试感觉离内存超限就差一点点了,还是得靠天时地利人和通过。当然,稳妥点的方法还是应该在代码上进行修改,运用其他的方式减少运行时间和需要的内存。
- 下一个坑就是一定要认真审题。我在写题目集三的7-3时,题目集二里也有一个日期的设计题目,因为改动内容不大(就是把方法改成一个日期类),就直接在源码上进行修改。改完之后提交,发现两个测试点并没能通过(看下图),便以为是代码自身存在缺陷,有地方考虑不够周全。但是看了好几遍代码也没能找出问题所在,交了几次也没能通过。
最后发现了是时间的上下限不同,发现只是因为这个问题看了很久的代码,我也很无奈。
然而我还在那找问题,也是没有注意到两个年份的合法取值范围是有不同的。之后的题目里,一定要注意审题,不要放过题目里的每一个细节。不然,一旦出现疏忽就得花很多时间来寻找这样一个细微的错误。这次的问题也是给我一个很大的提醒,我在今后也会注意这些。
- 最后是关于一个小细节。在第二次习题的7-8中,这道题是通过输入三角形三边边长来判断三角形的类型。在设计直角三角形中,需要用到勾股定理计算三角形三边长度间的关系。在测试点中,也是反映出了这个问题。
题目:
提交后测试点未通过代码:
import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner in = new Scanner(System.in); double i = in.nextDouble(); double j = in.nextDouble(); double k = in.nextDouble(); if(i < 1 || i > 200 || j < 1 || j > 200 || k < 1 || k > 200){ System.out.println("Wrong Format"); } else{ if(i + j > k && i + k > j && k + j > i){ if(i == j || j == k || i == k){ if((i*i+j*j==k*k && i != k)|| (k*k+j*j==i*i && j != k) || (i*i+k*k==j*j && i != j)){//勾股定理 System.out.println("Isosceles right-angled triangle"); } else if(i == j && j == k){ System.out.println("Equilateral triangle"); } else{ System.out.println("Isosceles triangle"); } } else if(i*i+j*j==k*k || k*k+j*j==i*i || i*i+k*k==j*j){ System.out.println("Right-angled triangle"); } else { System.out.println("General triangle"); } } else{ System.out.println("Not a triangle"); } } } }
提交结果:
我自己也是发现了问题改了代码
原来未通过的部分代码:
if((i*i+j*j==k*k && i != k)|| (k*k+j*j==i*i && j != k) || (i*i+k*k==j*j && i != j))
更改之后的代码:
if(Math.abs(i*i+j*j-k*k) < 0.001 || Math.abs(i*i+k*k-j*j) < 0.001 || Math.abs(k*k+j*j-i*i) < 0.001)
更改后进行测试:
其原因就是计算机运算的结果并不是十分精确。老师在上课时也强调过这个问题,计算机不会直接进行四则运算。这样一来,计算结果自然会有误差。那么这里运用的勾股定理,就自然不能直接使用两个直角边的平方和等于斜边平方的运算方式。而是使用两直角边平方和与斜边平方相减的差值小于一定值来进行运算,这样可以给计算机一定的误差空间。如果没有这个测试点的提醒,我可能是不会想起来要用到这种方式来解决这个问题。
四、改进建议
- 代码的复杂性有点偏高,有些方法里也未能实现单一职责原则,一个方法里可能会执行多种职责,导致代码的可读性降低。不仅如此,修改代码的时候也不大方便,通常是一个方法里出现了问题,就需要去改动较多的地方。虽然没有达到“牵一发而动全身”的地步,但是修改代码的过程会变得十分复杂,而且修改不当还会影响其他功能。关于这一点,我会在之后的题目中多加注意.
- 代码的可扩展性还较低。例如前三次题目集,有三题都是在设计日期,虽然有些部分可以直接使用,但是每做一次题代码的改动仍是不小。当然,这几次作业大多都是只涉及一个类的设计,还比较单一,也无法真正地体现出面向对象的程序设计。
- 在编写代码的时候,应该时刻注意代码的格式问题以及命名规则,例如命名方法名时使用“驼峰”的方式,给方法名和参数命名时避免中文和,空格的适当位置等,
- 避免无效代码或者代码冗余的出现。在题目集2中的7-5学号识别的题目中,因为输出的内容放在if语句外面会影响结果,我就简单粗暴地把输出的语句复制后放在语句之内,让代码变得十分繁琐。还好题目本身比较简单,代码数量较少,但是这种做法在今后是不可取的。
五、总结
-
- 通过这三次题目集的训练,很大程度上地巩固了Java的语法知识,以及对新学的知识的使用,完成了从C语言到Java面向对象课程的衔接。接触到类这个概念也不是很久,使用方面也有很多的不足和缺陷。除了这些问题之外,还有就是我自学的时间安排的太少了,没有合理安排、规划时间完成应该完成的学习任务以及我计划去完成的任务。当然,我在这次学习的过程中也收获颇丰。在这次的题目集中,我也看到了自己在知识点上的不足,逻辑思维方面的欠缺,也大概知道了今后应该学习的方向。不止如此,我也清楚了主动学习的重要性,不应该像中学阶段被老师推着学习,要在课余时间主动了解专业相关的知识,在之后的学习中不断改善。
- 目前课堂上的教学方式很好,老师提出学习内容,能让学生有学习的方向,而不是像无头苍蝇般的不知道该从何学起,自学的内容虽然可能不会学得很深,但是是很有必要的。