(1)前言
Java的知识绕不开它的封装、继承和多态。这次在PTA写的Java编程题也主要是帮助我去理解和体会它们吧,所以先说说关于这三个特性的知识点。
1、封装
何为封装?
把事物抽象成一个类,将事物拥有的属性和动作隐藏起来,只保留特定的方法与外界联系。当内部的逻辑发生变化时,外部调用不用因此而修改,它们只调用开放的接口,而不用去关心内部的实现。
而封装的好处是?
封装的好处则可分为以下三点,首先是实现了专业的分工,将处理逻辑封装成一个方法,做到见名知其义。然后是良好的封装能够减少耦合。最后是隐藏信息,实现细节。
2、继承
何为继承?
继承是面向对象的最显著的一个特征。继承是从已有的类(父类或者超类)中派生出新的类(子类),新的类能吸收已有类的数据属性和行为,并能扩展新的能力(方法的覆盖/重写)。JAVA不支持多继承,一个类只能有一个父类。父类是子类的一般化,子类是父类的特殊化(具体化)
(1)其中子类的特点
子类拥有父类非private的属性和方法
子类可以添加自己的方法和属性,即对父类进行扩展
子类可以重新定义父类的方法,即方法的覆盖/重写
(2)构造函数的特点
构造函数不能被继承,子类可以通过super()显示调用父类的构造函数,创建子类时,编译器会自动调用父类的无参构造函数,如果父类没有定义无参构造函数,子类必须在构造函数的第一行代码使用super()显示调用,由于后边要阐述多态,这里我们先来介绍一个重要的概念,即方法的覆盖/重写。
(3)覆盖/重写的概念
当子类需要修改父类的一些方法进行扩展,增大功能,程序设计者常常把这样的一种操作方法称为重写,也叫称为覆盖。
可以这么理解:重写就是指子类中的方法与父类中继承的方法有完全相同的返回值类型、方法名、参数个数以及参数类型。这样,就可以实现对父类方法的覆盖。如果子类将父类中的方法重写了,而我们想调用父类中的同名方法怎么办?此时,通过使用super关键就可以实现这个功能,super关键字可以从子类访问父类中的内容,如果要访问被重写过的方法,使用“super.方法名(参数列表)”的形式调用。
3、多态
何为多态?
多态的本质是:一个程序中同名的不同方法。在面向对象的程序设计中,多态主要有以下三种方式来实现。首先是通过子类对父类方法的覆盖来实现多态,然后是通过一个类中方法的重载来实现多态,再是通过将子类的对象作为父类的对象实现多态。
覆盖的概念我们在前面以及介绍了,接下来我们简单阐述下何为重载。
重载是指一个类里面(包括父类的方法)存在方法名相同,但是参数不一样的方法,参数不一样可以是不同的参数个数、类型或顺序。如果仅仅是修饰符、返回值、throw的异常 不同,那么这是2个相同的方法。
如何通过将子类的对象作为父类的对象实现多态?
把不同的子类对象都当作父类来看,可以屏蔽不同子类对象之间的差异,写出通用的代码,做出通用的编程,以适应需求的不断变化。赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。也就是说,父亲的行为像儿子,而不是儿子的行为像父亲。继承是面向对象语言中一个代码复用的机制,简单说就是子类继承了父类中的非私有属性和可以继承的方法,然后子类可以继续扩展自己的属性及方法。对象的引用型变量是具有多态性的,因为一个引用型变量可以指向不同形式的对象,即:子类对象作为父类对象来使用。在这里涉及到了向上转型和向下转型。下面再简述一下向上转型和向下转型。
向上转型是子类对象转为父类,父类可以是接口。公式:Father f = new Son(); Father是父类或接口,Son是子类。向下转型是父类对象转为子类。公式:Son s = (Son) f;在向上转型的时候我们可以直接转,但是在向下转型的时候我们必须强制类型转换。并且,如案例中所述,该父类必须实际指向了一个子类对象才可强制类型向下转型。若Father f = new Father()那么不可以转换,运行会报错。
知识点我只能简单地这样表述了,因为有关于封装、继承和多态的知识点比这要多得多,所以我就不一一详细论述了。在学习Java的过程就是在不断地理解和运用这三个知识点的过程。有了最基本的理解我们就可以利用不同的应用场景来设计对应的类来解决问题,下面我再来介绍一下这三次题目集的题量和难度。
首先是题目集四,一共有三道编程题,分别是水文数据校验及处理、日期问题面向对象设计(聚合)和图形继承。相对来说第一道水文数据检验及处理的题目比较综合,且难度更高一点。它考察了我们对字符串的处理,其中有关于正则表达式的部分要麻烦一些,然后就是我对整个题目类之间的关系以及它们的相关属性和方法的设计了。当然老师把这个题目的相应类图给了我,所以说在类的设计上没有碰到阻碍,主要就是对字符串的处理吧。其他两个题目就比较简单了,关于那个日期的题目下面我再重点说。而那个图形继承的题目光看题目的名字也知道是利用类之间的继承关系来解决,而且都是用到最基本的继承知识,比我上面说的那些还要少。
然后就是题目集五,总共有四道编程题,分别是日期问题面向对象设计(聚合二)、统计Java程序中关键词的出现次数 、合并两个有序数组为新的有序数组 和对整型数据排序。这四道题目我认为是第二道题目更难一些吧,第一题和题目集四的那一题差不多,后面我会重点说这两题。然后就是统计Java程序中关键词的出现次数的题目。用到的知识点也主要是正则表达式和对字符串的一些相关处理吧,难度对我来说刚刚好,主要是解题思路一定是不要太固化,后面我会分析这个题目的解题思路,自认为还是很清晰的思路(●'◡'●)。然后就是后面的两道题了,都是对整型数组的排序,这类题目在学习C语言的时候都做烂了,所以说没啥难度可言,主要的知识点是对数组排序的掌握,相关的几种排序算法。
最后是题目集六,一共有六道题,第一题是正则表达式训练-QQ号校验,这道题的知识点主要是最基本最简单的正则表达式,难度的话......你懂的。第二题是字符串训练-字符排序,知识点是根据Ascii码对字符进行排序输出。我用的是冒泡排序,也比较简单。第三题正则表达式训练-验证码校验和qq校验的那题几乎一样。第四题正则表达式训练-学号校验 还是老味道,简单基础的正则表达式。第五题 图形继承与多态,计算各种面积,有一些难度,主要是接口的使用。最后一题是实现图形接口及多态性,也是接口的使用,难度适中吧。
(2)设计与分析
①题目集4(7-2)、题目集5(7-4)两种日期类聚合设计的优劣比较
题目集4(7-2)
应用程序共测试三个功能:1.求下n天求2.前n天3.求两个日期相差的天数。相关类图为:

提交源代码为:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Scanner in=new Scanner(System.in);
int i,y,m,d,n;
i=in.nextInt();
if(i==1) {
y=in.nextInt();
m=in.nextInt();
d=in.nextInt();
n=in.nextInt();
DateUtil t=new DateUtil(y,m,d);
if(t.checkInputValidity()==false)
System.out.print("Wrong Format");
else {
t=t.getNextNDays(n);
System.out.print(t.day.month.year.value+"-"+t.day.month.value+"-"+t.day.value);
}
}
else if(i==2) {
y=in.nextInt();
m=in.nextInt();
d=in.nextInt();
n=in.nextInt();
DateUtil t=new DateUtil(y,m,d);
if(t.checkInputValidity()==false)
System.out.print("Wrong Format");
else {
t=t.getPreviousNDays(n);
System.out.print(t.day.month.year.value+"-"+t.day.month.value+"-"+t.day.value);
}
}
else if(i==3) {
int y1=in.nextInt();
int m1=in.nextInt();
int d1=in.nextInt();
int y2=in.nextInt();
int m2=in.nextInt();
int d2=in.nextInt();
DateUtil t1=new DateUtil(y1,m1,d1);
DateUtil t2=new DateUtil(y2,m2,d2);
if(t1.checkInputValidity()==false||t2.checkInputValidity()==false)
System.out.print("Wrong Format");
else
{
int sum=t1.getDaysofDates(t2);
System.out.print(sum);
}
}
else
System.out.print("Wrong Format");
}
}
class DateUtil{
Day day;
public DateUtil() {}//无参构造方法
public DateUtil(int y,int m,int d) {//带参构造方法
this.day=new Day(y,m,d);
}
public Day getDay() {
return this.day;
}
public void setDay(Day d) {
this.day=d;
}
public boolean checkInputValidity() {
if(this.day.month.year.vaLidate()==true&&this.day.month.validate()==true&&this.day.validate()==true)
return true;
else
return false;
}
public boolean compareDates(DateUtil date) {//比较两个日期的大小
if(this.day.month.year.value>date.day.month.year.value)//比较年
return true;
else if(this.day.month.year.value<date.day.month.year.value)
return false;
else if(this.day.month.year.value==date.day.month.year.value) {
if(this.day.month.value>date.day.month.value)
return true;
else if(this.day.month.value<date.day.month.value)
return false;
else if(this.day.month.value==date.day.month.value)
{
if(this.day.value>=date.day.value)
return true;
else
return false;
}
}
return true;
}
public boolean equalTwoDates(DateUtil date) {//判断两个日期是否相等
if(this.day.month.year.value==date.day.month.year.value&&this.day.month.value==date.day.month.value&&this.day.value==date.day.value)
return true;
else
return false;
}
public DateUtil getNextNDays(int n) {//求下N天
DateUtil d=new DateUtil();
d.day=this.day;
int i=0;
while(i<n) {
if(d.day.month.year.isLeapYear()==true&&d.day.month.value==2) {//闰年2月
if(d.day.value==29)//加月
{
d.day.month.monthIncrement();
d.day.resetMin();
}
else//加日
d.day.dayIncrement();
}
else {
if(d.day.value==d.day.mon_maxnum[day.month.value-1])
{
if(d.day.month.value<12)//加月
{
d.day.month.monthIncrement();
d.day.resetMin();
}
else//加年
{
d.day.month.year.yearIncrement();
d.day.month.resetMin();
d.day.resetMin();
}
}
else//加日
{
d.day.dayIncrement();
}
}
i++;
}
return d;
}
public DateUtil getPreviousNDays(int n) {//求前N天
DateUtil d=new DateUtil();
d.day=this.day;
int i=0;
while(i<n) {
if(d.day.month.year.isLeapYear()==true&&d.day.month.value==3) {//闰年3月
if(d.day.value==1)//减月
{
d.day.month.monthReduction();
d.day.value=29;
}
else//减日
d.day.dayReduction();
}
else {
if(d.day.value==1)
{
if(d.day.month.value>1)//减月
{
d.day.month.monthReduction();;
d.day.resetMax();
}
else//减年
{
d.day.month.year.yearReduction();
d.day.month.resetMax();
d.day.resetMax();
}
}
else//减日
{
d.day.dayReduction();
}
}
i++;
}
return d;
}
public int getDaysofDates(DateUtil date1) {
int n=0;
DateUtil d1=new DateUtil();
DateUtil d2=new DateUtil();
if(this.compareDates(date1)==true)
{
d1.day=date1.day;
d2.day=this.day;
}
else
{
d1.day=this.day;
d2.day=date1.day;
}
while(d1.equalTwoDates(d2)!=true)
{
if(d1.day.month.year.isLeapYear()==true&&d1.day.month.value==2) {//闰年2月
if(d1.day.value==29)//加月
{
d1.day.month.monthIncrement();
d1.day.resetMin();
}
else//加日
d1.day.dayIncrement();
}
else {
if(d1.day.value==d1.day.mon_maxnum[d1.day.month.value-1])
{
if(d1.day.month.value<12)//加月
{
d1.day.month.monthIncrement();
d1.day.resetMin();
}
else//加年
{
d1.day.month.year.yearIncrement();
d1.day.month.resetMin();
d1.day.resetMin();
}
}
else//加日
{
d1.day.dayIncrement();
}
}
n++;
}
return n;
}
}
class Day{
int value;
Month month ;
int [] mon_maxnum= {31,28,31,30,31,30,31,31,30,31,30,31};
public Day() {}//无参构造方法
public Day(int yearValue,int monthValue ,int dayValue) {//带参构造方法
this.month=new Month(yearValue,monthValue);
this.value=dayValue;
}
public int getValue() {//value getter
return this.value;
}
public void setValue(int value) {//value setter
this.value =value;
}
public Month getMonth() {//month getter
return this.month;
}
public void setMonth(Month month) {//month setter
this.month=month;
}
public void resetMin() {//日期改为1
this.value=1;
}
public void resetMax() {//日期改为最大值
if(month.year.isLeapYear()==true&&month.value==2)
this.value=29;
else
this.value=mon_maxnum[month.value-1];
}
public boolean validate() {//判断day的合法性
if(month.year.isLeapYear()==true&&month.value==2) {
if(this.value>=1&&this.value<=29)
return true;
else
return false;
}
else {
if(this.month.value>=1&&this.month.value<=12)
{
if(this.value>=1&&this.value<=mon_maxnum[month.value-1])
return true;
else
return false;
}
else
return false;
}
}
public void dayIncrement() {//日加1
this.value++;
}
public void dayReduction() {//日减1
this.value--;
}
}
class Month{
int value;
Year year;
public Month() {}//无参构造
public Month(int yearValue,int monthValue) {//带参构造
this.year= new Year(yearValue);
this.value=monthValue;
}
public int getValue() {//value getter
return this.value;
}
public void setValue(int value) {//value setter
this.value =value;
}
public Year getYear() {//year getter
return this.year;
}
public void setYear(Year year) {//year setter
this.year =year;
}
public void resetMin() {//月份复位1
this.value=1;
}
public void resetMax() {//月份复位12
this.value=12;
}
public boolean validate() {
if(this.value>=1&&this.value<=12)
return true;
else
return false;
}
public void monthIncrement() {//月份加1
this.value++;
}
public void monthReduction() {//月份减1
this.value--;
}
}
class Year{
int value;
public Year(){}
public Year(int value){//带参构造
this.value=value;
}
public int getValue() {
return this.value;
}
public void setValue(int value) {
this.value =value;
}
public boolean isLeapYear() {//判断是否为闰年
if((this.value%4==0&&this.value%100!=0)||(this.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() {//年份加1
this.value++;
}
public void yearReduction() {//年份减1
this.value--;
}
}
题目集5(7-4)题目要实现的内容相同,设计类图为:

提交源代码:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Scanner in=new Scanner(System.in);
int i,y,m,d,n;
i=in.nextInt();
if(i==1) {
y=in.nextInt();
m=in.nextInt();
d=in.nextInt();
n=in.nextInt();
DateUtil t=new DateUtil(y,m,d);
if(t.checkInputValidity()==false)
System.out.print("Wrong Format");
else {
t=t.getNextNDays(n);
System.out.print(y+"-"+m+"-"+d+" next "+n+" days is:"+t.day.month.year.value+"-"+t.day.month.value+"-"+t.day.value);
}
}
else if(i==2) {
y=in.nextInt();
m=in.nextInt();
d=in.nextInt();
n=in.nextInt();
DateUtil t=new DateUtil(y,m,d);
if(t.checkInputValidity()==false)
System.out.print("Wrong Format");
else {
t=t.getPreviousNDays(n);
System.out.print(y+"-"+m+"-"+d+" previous "+n+" days is:"+t.day.month.year.value+"-"+t.day.month.value+"-"+t.day.value);
}
}
else if(i==3) {
int y1=in.nextInt();
int m1=in.nextInt();
int d1=in.nextInt();
int y2=in.nextInt();
int m2=in.nextInt();
int d2=in.nextInt();
DateUtil t1=new DateUtil(y1,m1,d1);
DateUtil t2=new DateUtil(y2,m2,d2);
if(t1.checkInputValidity()==false||t2.checkInputValidity()==false)
System.out.print("Wrong Format");
else
{
int sum=t1.getDaysofDates(t2);
System.out.print("The days between "+y1+"-"+m1+"-"+d1+" and "+y2+"-"+m2+"-"+d2+" are:"+sum);
}
}
else
System.out.print("Wrong Format");
}
}
class DateUtil{
Day day;
public DateUtil() {}//无参构造方法
public DateUtil(int y,int m,int d) {//带参构造方法
this.day=new Day(y,m,d);
}
public Day getDay() {
return this.day;
}
public void setDay(Day d) {
this.day=d;
}
public boolean checkInputValidity() {
if(this.day.month.year.vaLidate()==true&&this.day.month.validate()==true&&this.day.validate()==true)
return true;
else
return false;
}
public boolean compareDates(DateUtil date) {//比较两个日期的大小
if(this.day.month.year.value>date.day.month.year.value)//比较年
return true;
else if(this.day.month.year.value<date.day.month.year.value)
return false;
else if(this.day.month.year.value==date.day.month.year.value) {
if(this.day.month.value>date.day.month.value)
return true;
else if(this.day.month.value<date.day.month.value)
return false;
else if(this.day.month.value==date.day.month.value)
{
if(this.day.value>=date.day.value)
return true;
else
return false;
}
}
return true;
}
public boolean equalTwoDates(DateUtil date) {//判断两个日期是否相等
if(this.day.month.year.value==date.day.month.year.value&&this.day.month.value==date.day.month.value&&this.day.value==date.day.value)
return true;
else
return false;
}
public DateUtil getNextNDays(int n) {//求下N天
DateUtil d=new DateUtil();
d.day=this.day;
int i=0;
int flag=0;
if(d.day.month.year.isLeapYear()==true) {//闰年
d.day.mon_maxnum[1]=29;
}
while(i<n) {
if(d.day.value==d.day.mon_maxnum[day.month.value-1])
{
if(d.day.month.value<12)//加月
{
d.day.month.monthIncrement();
d.day.resetMin();
}
else//加年
{
d.day.month.year.yearIncrement();
d.day.month.resetMin();
d.day.resetMin();
if(d.day.month.year.isLeapYear()==true) {//闰年
d.day.mon_maxnum[1]=29;
}
else
d.day.mon_maxnum[1]=28;
}
}
else//加日
{
d.day.dayIncrement();
}
i++;
}
return d;
}
public DateUtil getPreviousNDays(int n) {//求前N天
DateUtil d=new DateUtil();
d.day=this.day;
int i=0;
if(d.day.month.year.isLeapYear()==true) {//闰年
d.day.mon_maxnum[1]=29;
}
while(i<n) {
if(d.day.value==1)
{
if(d.day.month.value>1)//减月
{
d.day.month.monthReduction();;
d.day.resetMax();
}
else//减年
{
d.day.month.year.yearReduction();
d.day.month.resetMax();
d.day.resetMax();
if(d.day.month.year.isLeapYear()==true&&d.day.month.value==3) {//闰年
d.day.mon_maxnum[1]=29;
}
else
d.day.mon_maxnum[1]=28;
}
}
else//减日
{
d.day.dayReduction();
}
i++;
}
return d;
}
public int getDaysofDates(DateUtil date1) {
int n=0;
DateUtil d1=new DateUtil();
DateUtil d2=new DateUtil();
if(this.compareDates(date1)==true)
{
d1.day=date1.day;
d2.day=this.day;
}
else
{
d1.day=this.day;
d2.day=date1.day;
}
while(d1.equalTwoDates(d2)!=true)
{
if(d1.day.month.year.isLeapYear()==true&&d1.day.month.value==2) {//闰年2月
if(d1.day.value==29)//加月
{
d1.day.month.monthIncrement();
d1.day.resetMin();
}
else//加日
d1.day.dayIncrement();
}
else {
if(d1.day.value==d1.day.mon_maxnum[d1.day.month.value-1])
{
if(d1.day.month.value<12)//加月
{
d1.day.month.monthIncrement();
d1.day.resetMin();
}
else//加年
{
d1.day.month.year.yearIncrement();
d1.day.month.resetMin();
d1.day.resetMin();
}
}
else//加日
{
d1.day.dayIncrement();
}
}
n++;
}
return n;
}
}
class Day{
int value;
Month month ;
int [] mon_maxnum= {31,28,31,30,31,30,31,31,30,31,30,31};
public Day() {}//无参构造方法
public Day(int yearValue,int monthValue ,int dayValue) {//带参构造方法
this.month=new Month(yearValue,monthValue);
this.value=dayValue;
}
public int getValue() {//value getter
return this.value;
}
public void setValue(int value) {//value setter
this.value =value;
}
public Month getMonth() {//month getter
return this.month;
}
public void setMonth(Month month) {//month setter
this.month=month;
}
public void resetMin() {//日期改为1
this.value=1;
}
public void resetMax() {//日期改为最大值
if(month.year.isLeapYear()==true&&month.value==2)
this.value=29;
else
this.value=mon_maxnum[month.value-1];
}
public boolean validate() {//判断day的合法性
if(month.year.isLeapYear()==true&&month.value==2) {
if(this.value>=1&&this.value<=29)
return true;
else
return false;
}
else {
if(this.month.value>=1&&this.month.value<=12)
{
if(this.value>=1&&this.value<=mon_maxnum[month.value-1])
return true;
else
return false;
}
else
return false;
}
}
public void dayIncrement() {//日加1
this.value++;
}
public void dayReduction() {//日减1
this.value--;
}
}
class Month{
int value;
Year year;
public Month() {}//无参构造
public Month(int yearValue,int monthValue) {//带参构造
this.year= new Year(yearValue);
this.value=monthValue;
}
public int getValue() {//value getter
return this.value;
}
public void setValue(int value) {//value setter
this.value =value;
}
public Year getYear() {//year getter
return this.year;
}
public void setYear(Year year) {//year setter
this.year =year;
}
public void resetMin() {//月份复位1
this.value=1;
}
public void resetMax() {//月份复位12
this.value=12;
}
public boolean validate() {
if(this.value>=1&&this.value<=12)
return true;
else
return false;
}
public void monthIncrement() {//月份加1
this.value++;
}
public void monthReduction() {//月份减1
this.value--;
}
}
class Year{
int value;
public Year(){}
public Year(int value){//带参构造
this.value=value;
}
public int getValue() {
return this.value;
}
public void setValue(int value) {
this.value =value;
}
public boolean isLeapYear() {//判断是否为闰年
if((this.value%4==0&&this.value%100!=0)||(this.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() {//年份加1
this.value++;
}
public void yearReduction() {//年份减1
this.value--;
}
}
两个题目的大致解题思路几乎时一样的,两种不同的聚合方式在类图中就有所体现,其中最明显的感受就是第一个题的聚合关系是有层次关系的,更像是一层依附着一层。而第二题则是分散式的,日期的元素更像是独立的,没有第一题那样的层层依附关系,对我来说写这两个题都是把类的关系先处理好,然后在将其中的属性和方法完善起来,虽然说它们当中的大致属性和方法是差不多的,但是能明显感觉到的是题目集4的那题要更加繁琐,像是一个栈,里面是一层层的。而且每一层和其他相邻的层又有着密不可分的 关系。比如当我想访问year类当中的属性是要从实体开始层层深入,而题目集5的那题就不会了,它能将日期的不同元素分开来,想改变那个就直接访问其对应的方法就可以。所以在这个角度来看后者实现的更加灵活可变一些,提高了代码的复用性,也正符合了面向对象编程的独特优势。
②题目集4(7-3)、题目集6(7-5、7-6)三种渐进式图形继承设计的思路与技术运用(封装、继承、多态、接口等)
题目集3(7-3)的类图为:

提交源码:
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
// TODO 自动生成的方法存根
Control c=new Control();
c.doing();
}
}
class Control{
public Control() {
}
public void doing() {
int n;
Scanner in=new Scanner(System.in);
n=in.nextInt();
if(n<1||n>4)
System.out.print("Wrong Format");
else {
if(n==1) {
double redius,area;
redius=in.nextDouble();
if(redius<0)
System.out.print("Wrong Format");
else {
Circle c=new Circle();
c.setRadius(redius);
area=c.getArea();
System.out.print("Circle's area:"+String.format("%.2f", area));
}
}
if(n==2) {
double width,length,area;
width=in.nextDouble();
length=in.nextDouble();
if(width<0||length<0)
System.out.print("Wrong Format");
else {
Rectangle r=new Rectangle();
r.setWidth(width);
r.setLength(length);
area=r.getArea();
System.out.print("Rectangle's area:"+String.format("%.2f", area));
}
}
if(n==3) {
double redius,area,volume;
redius=in.nextDouble();
if(redius<0)
System.out.println("Wrong Format");
else {
Ball b=new Ball();
area=b.getArea(redius);
volume=b.getVolume(redius);
System.out.println("Ball's surface area:"+String.format("%.2f", area));
System.out.print("Ball's volume:"+String.format("%.2f", volume));
}
}
if(n==4) {
double width,length,height,area,volume;
width=in.nextDouble();
length=in.nextDouble();
height=in.nextDouble();
if(width<0||length<0||height<0)
System.out.print("Wrong Format");
else {
Box box=new Box();
box.setHeight(height);
area=box.getArea(width, length);
volume=box.getVolume(width, length);
System.out.println("Box's surface area:"+String.format("%.2f", area));
System.out.print("Box's volume:"+String.format("%.2f", volume));
}
}
}
}
}
class Shape{
public Shape(){
System.out.println("Constructing Shape");
}
public double getArea(double r) {
return 0;
}
}
class Circle extends Shape{
private double radius;
public Circle (){
System.out.println("Constructing Circle");
}
public double getArea() {
return Math.PI*this.radius*this.radius;
}
public void setRadius(double radius) {
this.radius=radius;
}
public double getRadius() {
return this.radius;
}
}
class Rectangle extends Shape{
private double width,length;
public Rectangle(){
System.out.println("Constructing Rectangle");
}
public double getArea() {
return this.width*this.length;
}
public void setWidth(double width) {
this.width=width;
}
public double getWidth() {
return this.width;
}
public void setLength(double length) {
this.length=length;
}
public double getLength() {
return this.length;
}
}
class Ball extends Circle{
public Ball() {
System.out.println("Constructing Ball");
}
public double getArea(double redius) {
return 4*Math.PI*redius*redius;
}
public double getVolume(double redius) {
return (4*Math.PI*redius*redius*redius)/3;
}
}
class Box extends Rectangle{
private double height;
public Box() {
System.out.println("Constructing Box");
}
public double getArea(double width,double length) {
return 2*width*length+2*length*this.height+2*width*this.height;
}
public double getVolume(double width,double length) {
return width*length*this.height;
}
public void setHeight(double height) {
this.height=height;
}
}
设计思路大概是根据类的继承关系来设计它们的属性及方法,图形类的父类shape类拥有一个计算面积的方法,还有它自己构造方法,然后不同的子类有它们自己属性,比如circle类有圆的半径,然后就是子类重写父类的getArea方法了,根据不同的计算方法来获得该图形的面积。这个题的精髓就在此吧,实现起来也比较简单。还有值得一提的地方就是存在多重的继承关系,就比如circle继承自shape类,然后ball类继承自circle类。还有就是当我们为子类创建一个实体实例时,它会先执行父类的构造方法,所以我们可以在shape类的构造方法里给出我们想要的输出字样。
题目集6(7-5、7-6)的两个类图分别为:


7-5源码:
import static java.lang.System.exit;
import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int c,r,t;
int sum=0,flag=0;
c=in.nextInt();
r=in.nextInt();
t=in.nextInt();
if (c < 0 || t < 0 || r < 0) {
System.out.println("Wrong Format");
exit(0);
}
double sumArea=0;
Circle []a=new Circle[c];
Rectangle []b=new Rectangle[r];
Triangle []w=new Triangle[t];
double[] area=new double [c+t+r];
for(int i=0;i<c;i++){
double radius=in.nextDouble();
a[i]=new Circle(radius);
area[sum++]=a[i].getArea();
if(a[i].validate()==false){
flag=1;
}
}
for(int i=0;i<r;i++){
double width=in.nextDouble();
double length=in.nextDouble();
b[i]=new Rectangle(width,length);
area[sum++]=b[i].getArea();
if(b[i].validate()==false){
flag=1;
}
}
for(int i=0;i<t;i++){
double side1=in.nextDouble();
double side2=in.nextDouble();
double side3=in.nextDouble();
w[i]=new Triangle(side1,side2,side3);
area[sum++]=w[i].getArea();
if(w[i].validate()==false){
flag=1;
}
}
if(flag==1)
{
System.out.println("Wrong Format");
exit(0);
}
System.out.println("Original area:");
for(int i=0;i<c+t+r;i++){
System.out.printf("%.2f ",area[i]);
sumArea+=area[i];
}
System.out.println();
System.out.print("Sum of area:");
System.out.printf("%.2f",sumArea);
System.out.println();
System.out.println("Sorted area:");
Arrays.sort(area);
for(int i=0;i<c+t+r;i++){
System.out.printf("%.2f ",area[i]);
}
System.out.println();
System.out.printf("Sum of area:%.2f",sumArea);
}
}
abstract class Shape{
public abstract double getArea();
public abstract boolean validate();
}
class Circle extends Shape{
private double radius;
public Circle () {};
public Circle(double radius) {
this.radius=radius;
}
public double getArea() {
return this.radius*this.radius*Math.PI;
}
public boolean validate() {
if(this.radius<=0)
return false;
else
return true;
}
}
class Rectangle extends Shape{
private double width;
private double length;
public Rectangle () {}
public Rectangle(double width,double length) {
this.length=length;
this.width=width;
}
public double getArea() {
return this.width*this.length;
}
public boolean validate() {
if(this.width<=0||this.length<=0)
return false;
else
return true;
}
}
class Triangle extends Shape{
double side1,side2,side3;
public Triangle() {}
public Triangle(double a,double b,double c) {
this.side1=a;
this.side2=b;
this.side3=c;
}
public double getArea() {
double p = (side1 + side2 + side3) / 2;
return Math.sqrt(p * (p - side1) * (p - side2) * (p - side3));
}
public boolean validate() {
if(side1<=0||side2<=0||side3<=0||side1+side2<=side3||side1+side3<=side2||side2+side3<=side1)
return false;
else
return true;
}
}
7-6源码:
import static java.lang.System.exit;
import java.util.Scanner;
import java.util.Arrays;
public class Main {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
double c,r,t;
int sum=0,flag=0;
c=in.nextDouble();
r=in.nextDouble();
t=in.nextDouble();
double sumArea=0;
Circle a=new Circle(c);
Rectangle b=new Rectangle(r,t);
if(a.validate()==false||b.validate()==false)
{
System.out.println("Wrong Format");
exit(0);
}
System.out.printf("%.2f\n", a.getArea());
System.out.printf("%.2f", b.getArea());
}
}
interface GetArea{
public abstract double getArea();
}
class Circle implements GetArea{
private double radius;
public Circle () {};
public Circle(double radius) {
this.radius=radius;
}
public double getArea() {
return this.radius*this.radius*Math.PI;
}
public boolean validate() {
if(this.radius<=0)
return false;
else
return true;
}
}
class Rectangle implements GetArea{
private double width;
private double length;
public Rectangle () {}
public Rectangle(double width,double length) {
this.length=length;
this.width=width;
}
public double getArea() {
return this.width*this.length;
}
public boolean validate() {
if(this.width<=0||this.length<=0)
return false;
else
return true;
}
}
分析:首先是两个题目都用到了继承,第一个题目的继承关系由类图都可以看出来,是一种相当直接的继承关系,这个题目的大概解题思路其实跟我上面讲的那个图形继承的题目差不多,不同的是对抽象类shape的设计,还有在它下面的实体性的子类的设计,编写代码也很简单,获取面积的方法也和上一题相差无几。值得注意的是声明存有实体对象的数组,可以理解为为每个实体类创建了一块属于它的存储空间,在你要对它进行操作的时候要准确地找到它,千万不能混淆。然后后面那个题目地重点是对接口使用,和它有关的关键字interface和implements。我谈谈我刚开始对这个题目接口的看法,我刚开始也不明白接口是什么,有什么作用。我就将它理解成抽象的父类,和实体化继承它的子类,事实上也是真的可以这么去理解的。在子类中必须要实现抽象父类的所有方法,但是我们可以对这些方法进行重写,让它实现我们想要实现的功能,这样就十分地方便,极大地提高了我们代码的复用性。
③对三次题目集中用到的正则表达式技术的分析总结
关于这些题目的正则表达式的技术,我就说两个比较有代表性的题目吧,因为这两个题目的正则表达式相对来说比较复杂一些,更加具有代表性一点。先是那个题目集4的7-1 水文数据校验及处理,这个题目要对输入的水文信息进行正则判定。实现的过程比较复杂,在这我就不赘述了。而这些正则中较为复杂的就是对输入的时间信息的判断,先让你看看我那可怕的正则表达式吧。时间信息正则表达式:(((([48])|([2468][048])|([13579][26])|(([1-9])(0[48]|[2468][048]|[13579][26]))|(([1-9][0-9])(0[48]|[2468][048]|[13579][26]))|(([48]|[2468][048]|[13579][26])00))/((([13578]|1[02])/([1-9]|[12][0-9]|3[01])|([469]|11)/([1-9]|[12][0-9]|30)|(2/([1-9]|[1][0-9]|2[0-9])))))|((([1235679])|([13579][01345789])|([2468][1235679])|(([1-9])(0[1235679]|[13579][01345789]|[2468][1235679]))|(([1-9][0-9])(0[1235679]|[13579][01345789]|[2468][1235679]))|(([1235679]|[13579][01345789]|[2468][1235679])00))/(([13578]|1[02])/([1-9]|[12][0-9]|3[01])|([469]|11)/([1-9]|[12][0-9]|30)|(2/([1-9]|[1][0-9]|2[0-8]))))) (([02468]|1[02468]|2[0-2]):00)。肯定有简单又方便的正则啦,这是我纯手工打造,写了两个多小时呜呜呜...我的解决思路就是先分闰年和平年,而年份又有两位数、三位数和四位数的,先从两位数的年份开始判断它的闰年平年情况,这就是看最后一位是何数字结尾了,其实它们有规律的,比如单位的就是4和8结尾就是闰年了,两位的可分为2468带上048结尾的就是闰年,或者是13579带上26结尾的是闰年,所以就简单的将闰年和平年区分开了,接下来就是对特殊的月份2月的处理了,所以分开闰年和平年,将2月的最后一天将它们划分开,基本上这个正则表达式就写好了。对我来说主要就是对闰年平年的处理比较麻烦,还有就是时间的正则了,我是这样写的(([02468]|1[02468]|2[0-2]):00),这个就很明了了吧,这个题要的是整点,所以就是:00结尾了。
再来说说题目集5(7-4)的正则表达式吧,大概背景是我要消除//开头的字符串,还有就是/* */之间的字符串,解决方法就是用replaall方法将空格替换掉正则表达式匹配的模式。所以问题的关键字在于正则表达要怎么写,刚开始我是这样写的.*//(.*) 后来发现有的点过不去,这就要用到正则的贪婪以及非贪婪了,那个/* */ 的我也是用的贪婪写的,比如/*abcd*/ ef /*ghij*/,正常来说我的注释是有两段需要删除的,但是我用贪婪写的是/\\*.*\\*/ 这样输出的结果是将整个/*abcd*/ ef /*ghij*/整个全部替换掉了,对于这个题目这就很不科学了,后面我用非贪婪是这样写的/\\*.?*\\*/ 这样替换后的结果就是 ef 了。
其他题目的正则表达式技术就比较直接暴力了,没有太多的分析必要了。
④题目集5(7-4)中Java集合框架应用的分析总结
这个题目的正则技术我上面已经将值得注意的细节说完了,那我就讲讲这个题目的解题思路吧。先是我要明白输入的代码如果是一行一行的话那就要用字符串数组逐条处理了,显然这是非常麻烦的,所以我的想法是将输入的代码将它们和成一行,这里就要用到StringBuilder类以及它的append方法了,用它的原因是它真的很方便将逐行的字符串转换为一行,而且StringBuilder类具有很好的安全性和空间利用能力。将输入的代码转化为一行后就要将注释部分删除掉了(//注释部分在转化为一行就要删除掉了,具体实现方法见源码),用空格替换正则/\\*.?*\\*/ 匹配的模式就可以。现在得到的就是没有注释的一行超长代码了,然后我们要做到就是将其中的单词剥离出来。我们要用到刚才的空格了,就是以空格将整行分为一个个的单词,并将它们存在一个字符串数组当中去。此处要用到split方法将它们分离。然后就是比对并计数了,这就相当的简单暴力了,循环比对就🆗了,然后将比对成功的在对应的单词那里几个数就可以。
提交源码:
import java.time.*;
import java.time.format.*;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
StringBuilder sb=new StringBuilder();
int flag=0;
while(true) {
String s=in.nextLine();
if(s.equals("exit"))
break;
else
{
flag=1;
if(s.matches(".*?//(.*?)")==true)
{
String s1[]=s.split("//");
sb.append(s1[0]+" ");
}
else
{
sb.append(s+" ");
}
}
}
if(flag==0)
System.out.print("Wrong Format");
else
{
String s2;
s2=sb.toString();
s2=s2.replaceAll("/\\*.*?\\*/", " ");//去掉/* */
s2=s2.replaceAll("\"([^\"]*)\"", " ");//去掉" "
s2=s2.replaceAll("=","yl");
s2=s2.replaceAll("[^a-zA-Z0-9]", " ");//将非字母数字转空格
String qi[]=s2.split(" ");
int len=qi.length;
int i=0,j;
String [] list1=new String[]{"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const",
"continue", "default", "do", "double", "else", "enum", "extends", "false", "final", "finally",
"float", "for", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long",
"native", "new", "null", "package", "private", "protected", "public", "return", "short", "static",
"strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "true", "try",
"void", "volatile", "while"};
int [] list2=new int[53];
while(i<len)
{
for( j=0;j<53;j++)
{
if(qi[i].equals(list1[j]))//匹配成功
{
list2[j]++;
break;
}
}
i++;
}
i=0;
while(i<53)
{
if(list2[i]!=0)
{
System.out.println(list2[i]+" "+list1[i]);
}
i++;
}
}
}
}
(3)采坑心得:其实我这三次作业踩了很多很多的坑,其中最最重要的还是正则表达式的坑吧,就比如因为没有写非贪婪模式

让我一直有两三个点过不去,所以下次写正则的时候应该根据题目的背景,考虑更加全面些。还有的坑大多就是逻辑上的坑了,那是无法避免的,因为逻辑上一定会有纰漏的。
踩坑的话我就不多说了,应为前面的模块我已经排了很多的坑了(篇幅已经很长了wuwuwu)。
(4)改进建议:题目集4 7-1 水文数据校验及处理 ,这个题目我的正则表达式写的太繁琐了,可以做相应的修改,让它更加精简些。还有就是这个题目我是用字符串数组来存储和处理收录的水文信息,这里我大可使用StringBuilder类来处理数据,这样可以节约很多被浪费的储存空间,也让后面的数据操作更加简单精炼。还有就是题目集5的7-5 日期问题面向对象设计(聚合二),可以优化求下n天、前n天以及两个日期间相差的天数的计算算法,在找到对应的日期的时候break出来,减少循环跑的数量,以此来缩减程序的运行时间。其他题目的话就适当优化整体的结构,提高代码的复用性,当我们再想要添加功能的时候能尽量少地修改原有的代码。
(5)总结:本阶段的三次题目集更加侧重于对类的设计,而不是单单像上次的题目集一样注重于解决问题本身的算法,类的设计往往是仁者见仁,不能说谁的好谁的不好。但是的话要将Java的封装、继承和多态体现出来,这样写出来的代码在结构上就十分的清晰,并且在后续修改和增添的过程中能有更好的兼容性。所以说在设计类的时候就要着眼全局,一个类解决一件事情、封装一个职责。类和类之间的关系要十分明了,规范好它们各自在程序中扮演的角色,发挥面向对象编程的优势。
浙公网安备 33010602011771号