经过三周java训练集的练习,让我们从C语言到java有了一个很好的过度,通过三次题目集的练习,让我认识到java基础语法与C语言是相似的,但java的面向对象编程是一种全新的概念,对java类的学习让我初步了解到什么是面向对象,面向对象重视的不仅仅是编码,更在于程序的设计。
前言:
作业相关知识点:
1.第一次作业主要是对于java基本语法,输入(如何用Scanner类去进行数据的输入),输出(System类)的针对性练习,和String类相关方法的使用;
2.第二次作业对第一次作业的进一步补充,提高我们对java的熟练度,还是涉及输入、输出及一些基本的字符串操作;
3.第三次作业是面向对象编程的开始,进行类的设计(简化题目解决方案),类的编写规范(类中该有的属性、方法),类的封装等。
三次题目集,知识点由浅到深,让我们严格规范自己的编码风格并对全新的面向对象编程思维有慢慢适应的过程,恰到好处。
作业题量:
三次作业题量为12+9+4,第一、二次涉及基本语法,第三次主要对类与对象进行练习。
作业难度:
按照五颗星难度标准,在我看来,第一次作业 7-10(处理多个字符串) 7-11(理解定积分)难度为2★,其他题目为1★; 第二次作业 7-8(精度丢失),7-9两题难度为3★,其他题目为2★; 第三次作业 7-3难度为3★,7-4难度为4★,其他题目为2★。
设计与分析:
1.GPS数据处理(第一次作业7-10)
题意非常清晰,读取多行字符串,按' , '把字符串用split()方法分割成多段,存入字符串数组中,用equals()方法判断字符串是否以"$GPRMC"开头,是则使用indexof()方法找到字符串中检验符' * '的下标,循环得到' $ '和' * '之间所有字符依次异或的值,得到对65536取余后的结果,和'*' 后面的两个十六进制数字的值进行比较,相等则传输正确可以进行下一步,否则处理下一个字符串,判断字段2即字符串数组中下标为1的字符串是否为" A "(定位状态),满足上述条件,即读取字段1的UTC时间转换为北京时间按hh:mm:ss进行输出。
题目内容很长,但需要你干的事解释的很清楚,只需按照题意实现相应的判断即可,注意转换时间不足10即要补0。
实现代码:
import java.util.*;
// 接受多行字符串,找到$GPRMC开头的 进行分割
// 把定位状态的A的时间记录
// 计算$ *之间的字符异或运算再检验
public class Main{
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String str = null,time = null;
str=input.nextLine();
while(!str.equals("END")) {
int sum = 0;// $*之间字符的异或值
String[] split = str.split(",");
if(split[0].equals("$GPRMC")) {// 找到正确语句
int end = str.indexOf("*");
for(int i = 1;i < end;i++) {
sum ^= (int)str.charAt(i);
}
sum%=65536;
Boolean s_flag=false;
Boolean flag=split[2].equals("A");// 定位状态下
int correctCheckSum = Integer.parseInt(str.substring(end+1),16) ;
if(sum == correctCheckSum){
s_flag = true;// 校验正确
}
if(s_flag == true&&flag == true) {
time = split[1];
}
}
str = input.nextLine();
}
if(time != null) {
int h = (Integer.parseInt(time.substring(0, 2))+8)%24;
String m = time.substring(2, 4);
String s = time.substring(4, 6);
if(h<10) System.out.print("0");
System.out.print(h+":"+m+":"+s);
}
}
}
SourceMonitor分析如下:
Max Complexity(最大圈复杂度)和Avg Complexity(平均圈复杂度)较高:if嵌套使用较多,
因为有多个前提条件需要判断,和多行输入使用while ,老师说学了继承以后可以改进(后续再说);
因为只写了一个主函数,Max Depth(最大函数深度)也比较高。
2.判断三角形类型:
题目主要考察解决double运算时的精度丢失问题,单个double数可能就有损失精度, 多个double数运算(勾股定理判断直角三角形)可能导致损失的精度更多,导致在判断等腰直角三角形时报错。可以使用计算误差小于0.00001时两者大致相同,或者引入BigDecimal类来防止精度丢失。
实现代码:
import java.util.*;
import java.math.BigDecimal;
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
double a,b,c;
a = input.nextDouble();
b = input.nextDouble();
c = input.nextDouble();
BigDecimal a1 = new BigDecimal(Double.toString(a));
BigDecimal b1 = new BigDecimal(Double.toString(b));
BigDecimal c1 = new BigDecimal(Double.toString(c));
BigDecimal a2 = a1.multiply(a1);
BigDecimal b2 = b1.multiply(b1);
BigDecimal c2 = c1.multiply(c1);
if( a<1 || a>200 || b < 1 || b > 200 || c < 1 || c > 200) {
System.out.print("Wrong Format");
}
else if(a+b <= c || a+c <= b || b+c <= a) {
System.out.print("Not a triangle");
}
else if(a == b && b == c) {
System.out.print("Equilateral triangle");
}
else if((a2.add(b2)).compareTo(c2)==0||(a2.add(c2)).compareTo(b2)==0||(b2.add(c2)).compareTo(a2)==0){
System.out.print("Right-angled triangle");
}
else if(a == b || b == c || a == c) {
if((a*a+b*b-c*c < 0.00001)||(a*a+c*c-b*b < 0.00001)||(b*b+c*c-a*a < 0.00001)) {
System.out.println("Isosceles right-angled triangle");
}else {
System.out.print("Isosceles triangle");
}
}
else {
System.out.print("General triangle");
}
}
}
SourceMonitor分析:
由于使用了大量的if-else来判断三角形的类型,Max Complexity(最大圈复杂度)和Avg Complexity(平均圈复杂度)较高,
只用了Main一个主类,还是面向过程的编程。
3.定义日期类(第三次题目集7-3):
通过调用Date 类里面的方法来实现求下一天,大致方法与第二次题目集的求下一天相同,但从面向过程向面向对象开始过渡,学着定义类中属性,构造方法,和求解方法。
Date类结构
SourceMonitor分析:
因为checkInputValidity()方法中使用了较多的 if 来判断输入是否合法,所以Max Complexity(最大圈复杂度)较高,但其他地方对分支的使用较少,所以Avg Complexity(平均圈复杂度)在理想区域内。
注意checkInputValidity()方法中,输入的月份天数要在本年的月份最大天数范围内,闰年2月最大天数为29,非闰年2月最大天数要改为28。
public boolean checkInputValidity() {
if(!isLeapYear(this.year)) {
mon_maxnum[2]=28;
}
if(this.year >= 1900 && this.year <= 2000 &&
this.month <= 12 && this.month >= 1) {
if(this.day <= mon_maxnum[month] && this.day >= 1) {
return true;
}else {
return false;
}
}else {
return false;
}
}
实现代码:
import java.util.*;
class Date {
private int year;
private int month;
private int day;
public int[] mon_maxnum =new int[]{0,31,29,31,30,31,30,31,31,30,31,30,31};
Date() {
}
Date(int year, int month, int day) {
this.year = year;
this.month = month;
this.day = day;
}
public int getYear() {
return this.year;
}
public void setYear(int year) {
this.year = year;
}
public int getMonth() {
return this.month;
}
public void setMonth(int month) {
this.month = month;
}
public int getDay() {
return this.day;
}
public void setDay(int day) {
this.day = day;
}
public boolean isLeapYear(int year) {
if((this.year%4 == 0 && this.year%100 != 0) || this.year%400 == 0) {
return true;
}else {
return false;
}
}
public boolean checkInputValidity() {
if(!isLeapYear(this.year)) {
mon_maxnum[2]=28;
}
if(this.year >= 1900 && this.year <= 2000 &&
this.month <= 12 && this.month >= 1) {
if(this.day <= mon_maxnum[month] && this.day >= 1) {
return true;
}else {
return false;
}
}else {
return false;
}
}
public void getNextDate() {
if(!isLeapYear(this.year)) {
mon_maxnum[2] = 28;
}
this.day++;
if(this.day>mon_maxnum[this.month]) {
this.day -= mon_maxnum[this.month];
this.month++;
}
if(this.month > 12) {
this.month = 1;
this.year += 1;
}
System.out.print("Next day is:" + this.year + "-" + this.month + "-" + this.day);
}
}
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
int year = input.nextInt();
int month = input.nextInt();
int day = input.nextInt();
Date date = new Date(year,month,day);
if(!date.checkInputValidity()) {
System.out.print("Date Format is Wrong");
}else {
date.getNextDate();
}
}
}
4.日期类设计(第三次题目集7-4):
在7-3的前提上改成了求n天,可以在7-3中getNextDate()方法的基础上进行改进,求两个日期差要判断给出日期的大小(先后),再分别从年,月,日开始计算。
DateUtil类结构:
SourceMonitor分析:
从图中可以看出我在getDaysofDates()方法中使用了较多的if while 导致我的执行路径变多,Max Complexity 最大圈复杂度较高,平均圈复杂度在可接受范围内。
在求下n天时,输入的n为int 最大值时,我在day的基础上加n来判断会导致超出范围,所以我引入了long来避免超过范围。
public DateUtil getNextNDays(int n) {
long d = this.day + (long)n;
while(d > mon_maxnum[this.month]) {
if(!isLeapYear(this.year)) {
mon_maxnum[2] = 28;
}else {
mon_maxnum[2] = 29;
}
d -= mon_maxnum[this.month];
this.month++;
if(this.month > 12) {
this.month = 1;
this.year += 1;
}
}
this.day = (int)d;
return this;
}
求前n天时我用的是天数相减所以不用担心超出范围
public DateUtil getPreviousNDays(int n) {
this.day -=n;
while(this.day < 1) {
if(!isLeapYear(this.year)) {
mon_maxnum[2] = 28;
}else {
mon_maxnum[2] = 29;
}
this.month--;
if(this.month < 1) {
this.month = 12;
this.year -= 1;
}
this.day += mon_maxnum[this.month];
}
return this;
}
求两个日期天数差,要按日期先后分两种情况计算
public int getDaysofDates(DateUtil date) {
int days=0;
if(this.equalTwoDates(date)) { // 相同日期
return 0;
}else{
if(this.year < date.year) {
while(this.year < date.year) {
if(isLeapYear(this.year)) {
days += 366;
}else {
days += 365;
}
this.year++;
}
}else {
while(this.year > date.getYear()) {
if(isLeapYear(date.year)) {
days += 366;
}else {
days += 365;
}
date.year++;
}
}
if(this.month > date.month) {
while(this.month > date.month) {
if(isLeapYear(date.year)) {
mon_maxnum[2] = 29;
}else {
mon_maxnum[2] = 28;
}
days -= mon_maxnum[date.month];
date.month++;
}
}else {
while(this.month < date.month) {
if(isLeapYear(date.year)) {
mon_maxnum[2] = 29;
}else {
mon_maxnum[2] = 28;
}
days += mon_maxnum[this.month];
this.month++;
}
}
if(this.day > date.day) {
days -= this.day-date.getDay();
}else {
days += date.getDay()-this.day;
}
}
return days;
}
踩坑心得:
1.java字符串比较是否相等要用equals() ,不可以直接用等号,在写第一次题目集的GPS数据处理时,一开始用 "==" 来判断直接编译错误,所以说java的一些语法还是与c语言不同的,有自己的特点,不能直接套用;
2.对输出格式把握不准,老是根据输出案例来设置保留多少位小数,在第二次题目集的7-1中就犯了这个错误,题目要求的应该是按float型输出,我直接保留1位小数输出,导致只过了样例,所以对于输出格式的控制还是要严谨一点,有时候格式也是答案错误的重要原因;
3..在第二次题目集判断三角形类型时,犯了个逻辑错误,把等腰三角形放在了等腰直角三角形之前,导致我等腰直角三角形被视为了等腰三角形输出,没有进入到等腰直角三角形的判断中,导致我在使用了BigDecimal保留精度后还以为是精度的问题。在多个条件有关联时要分清主次!
4.2月的最大天数要随是否为闰年这个条件改变,平年2月只有28天,一开始在判断输入合法时,直接把输入天数限定在1到31(这样平年2月29也是正确输入),没有根据月份天数来,导致一个测试点没过,考虑应该要全面一点,不要交交改改,下次要想清楚再交,争取能避免多次提交;
5.注意程序的死循环,在第三次题目集7-4求两个日期相差天数是,我先用了while循环计算年份,月份的天数,计算完两个日期年,月应该是相同的,然后退出循环,在天的差上我也直接套用这个方法,但忘记计算完把两个天数统一了,导致我一直卡在死循环里,因为样例的天是相同的所以我没发觉,花了好大功夫才找到这个死循环。
改进建议:
1.第一次题目集去掉重复字符串,我用了两重for循环和标记数组来解决,时间复杂度较高,可以使用replace和正则表达式来进行字符串去重;
所以这里替换使用的是replaceAll()
public class Main{
public static void main(String args[]){
Scanner input = new Scanner(System.in);
String s1 = input.nextLine();
String s2 = input.nextLine();
s1 = s1.replaceAll("["+s2+"]",""); //s2中的全部字符
System.out.print(s1);
}
}
2.第三次题目集7-3的类中,getNextDate()方法没有满足单一职责的原则,不应该把输出下一天放在之中;
public void getNextDate() {
if(!isLeapYear(this.year)) {
mon_maxnum[2] = 28;
}
this.day++;
if(this.day>mon_maxnum[this.month]) {
this.day -= mon_maxnum[this.month];
this.month++;
}
if(this.month > 12) {
this.month = 1;
this.year += 1;
}
System.out.print("Next day is:" + this.year + "-" + this.month + "-" + this.day);
}
改进方法:可以写一个printNextDate()输出
public void printNextDate() {
System.out.print("Next day is:" + this.year + "-" + this.month + "-" + this.day);
}
3.这三次题目集有关if-else判断的题目的圈复杂度都很高,但目前我还无法对此进行改进,老师说不是不可以改进是我们还没能力去改进,再学到继承后再回头来看看。
总结:
三次题目集从基础语法,循环,输入,输出到类的设计与使用,让我对java这门现在大热的语言有了初步的认识,简单来说前面两次题目集是让我们熟悉怎么来用java,解题的时候还是在面向过程,第三次题目集我们用到了类,开始过渡到面向对象编程,但老师贴心的为我们把类都设计好了,后面我们应该会自己学着去设计类图,要有这种思维,要学会自己动手。
下一步计划:
1.继续借鉴阿里的开发手册,规范编码,提高代码可读性和可扩展性;
2.熟悉类的使用,学着自己设计类,可以借助PowerDesigner来实体化类的设计,同时了解类与类之间的关系,通过多个类来简化解题的步骤;
3.把集合框架熟悉下,ArraryList 和 Set 等都是解题的便捷方法(特别在数组排序,去重等方面),并且可以减少时间复杂度,防止数据过大时运行超时,我觉得排序器(Comparator)的设计非常人性化,给了我们自己去定义排序方法的操作。