l 前言
- 众所周知,博主是每天在pta上更的文章,本次依旧是基于pta平台习题的针对性分析,跟大家侃侃我眼中的继承与多态。
- 最近的题目集涉及到的都是继承与多态的相关知识,题量较多,为的是让我们尽快理解继承和多态,难度个人认为中偏上。
- 解题思路倒是清晰明了,有的需要实现类图,有的则是清晰的告诉解题者设计思路。
l 设计与分析
一、题目集4(7-2)、题目集5(7-4)两种日期类聚合设计的优劣比较
参考题目7-2的要求,设计如下几个类:DateUtil、Year、Month、Day,其中年、月、日的取值范围依然为:year∈[1900,2050] ,month∈[1,12] ,day∈[1,31] , 设计类图如下:

应用程序共测试三个功能:
- 求下n天
- 求前n天
- 求两个日期相差的天数
输入格式:
有三种输入方式(以输入的第一个数字划分[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且输入均有效,输出格式如下:
天数值
源代码如下:
import java.util.*;
class Year{
private int value;
Year(int value)
{
this.setValue(value);
}
public Year() {
// TODO 自动生成的构造函数存根
}
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(value>=1900&&value<=2050)
return true;
else
return false;
}
public void yearIncrement()
{
value++;
}
public void yearReduction()
{
value--;
}
}
class Month{
private int value;
private Year year=new Year();
public Month(int value, int yearvalue) {
this.value=value;
this.year.setValue(yearvalue);
}
public int getValue() {
return value;
}
public Month() {
}
public Year getYear() {
return year;
}
public void setValue(int value) {
this.value = value;
}
public void setYear(int year) {
this.year.setValue(year);
}
public void resetMin() {
this.value=1;
}
public void resetMax() {
this.value=12;
}
public void monthIncrement()
{
value++;
}
public void monthReduction()
{
value--;
}
public boolean validate() {
if(value>=1&&value<=12)
return true;
else return false;
}
}
class Day {
private int value;
private Month month;
private int[] mon= {31,28,31,30,31,30,31,31,30,31,30,31};
Day(){
}
Day(int year,int month,int day){
this.value=day;
this.month=new Month();
this.month.setValue(month);
this.month.setYear(year);
}
public int getValue() {
return value;
}
public Month getMonth() {
return month;
}
public void setValue(int value) {
this.value = value;
}
public void setMonth(int month) {
this.month.setValue(month);
}
public void resetMin() {
this.value=1;
}
public void resetMax() {
if(month.getValue()==0)
this.value=mon[11];
else this.value=mon[month.getValue()-1];
}
public boolean validate() {
leapYear();
if(this.value>=1&&this.value<=mon[this.month.getValue()-1])
return true;
else return false;
}
public void leapYear() {
if(month.getYear().isLeapYear())
mon[1]=29;
else mon[1]=28;
}
public void dayIncrease() {
this.value++;
}
public void dayReduce() {
this.value--;
}
}
class DateUtil{
private Day day;
public DateUtil() {
// TODO 自动生成的构造函数存根
}
public DateUtil(int d,int m,int y) {
this.day=new Day(y,m,d);
}
public Day getDay() {
return day;
}
public void setDay(Day day) {
this.day = day;
}
public boolean Check() {
if(day.validate()&&day.getMonth().validate()&&day.getMonth().getYear().validate())
return true;
else return false;
}
public boolean compareDate(DateUtil date) {
if(day.getMonth().getYear().getValue()>date.day.getMonth().getYear().getValue())
return true;
else if(day.getMonth().getYear().getValue()<date.day.getMonth().getYear().getValue())
return false;
else if(day.getMonth().getYear().getValue()==date.day.getMonth().getYear().getValue())
{
if(day.getMonth().getValue()>date.day.getMonth().getValue())
return true;
else if(day.getMonth().getValue()<date.day.getMonth().getValue())
return false;
else if(day.getMonth().getValue()==date.day.getMonth().getValue())
{
if(day.getValue()>date.day.getValue())
return true;
else if(day.getValue()<date.day.getValue())
return false;
}
}
return false;
}
public boolean equal(DateUtil date) {
if(day.getValue()!=date.day.getValue())
return false;
else if(day.getMonth().getValue()!=date.day.getMonth().getValue())
return false;
else if(day.getMonth().getYear().getValue()!=date.day.getMonth().getYear().getValue())
return false;
else return true;
}
String showDate()
{
String a=Integer.toString(day.getMonth().getYear().getValue())+"-"+Integer.toString(day.getMonth().getValue())+"-"+Integer.toString(day.getValue());
return a;
}
public DateUtil getnextday(int n) {
day.leapYear();
for(int i=0;i<n;i++) {
day.dayIncrease();
if(!day.validate()) {
day.resetMin();
day.getMonth().monthIncrement();
}
if(!day.getMonth().validate()) {
day.getMonth().resetMin();
day.getMonth().getYear().yearIncrement();
day.leapYear();
}
}
DateUtil a=new DateUtil(day.getValue(),day.getMonth().getValue(),day.getMonth().getYear().getValue());
return a;
}
public DateUtil getpreviousday(int n) {
day.leapYear();
for(int i=0;i<n;i++) {
day.dayReduce();
if(!day.validate()) {
day.getMonth().monthReduction();
day.resetMax();
}
if(!day.getMonth().validate()) {
day.getMonth().resetMax();
day.getMonth().getYear().yearReduction();
day.leapYear();
}
}
DateUtil a=new DateUtil(day.getValue(),day.getMonth().getValue(),day.getMonth().getYear().getValue());
return a;
}
public int getDaysOfNexttDates(DateUtil date) {
int i=0;
boolean b=compareDate(date);
if(b) {
while(!equal(date)) {
i++;
date.getnextday(1);
}
}else {
while(!equal(date)) {
i++;
date.getpreviousday(1);
}
}
return i;
}
}
public class Main {
public static void main(String[] args) {
Scanner in=new Scanner(System.in);
int a=in.nextInt();
int y=in.nextInt();
int m=in.nextInt();
int d=in.nextInt();
int k;
if(a==1) {
k=in.nextInt();
DateUtil date1=new DateUtil(d,m,y);
if(m>12||m<1) {
System.out.print("Wrong Format");
System.exit(1);
}
if(!date1.Check()) {
System.out.print("Wrong Format");
System.exit(1);
}
DateUtil an=date1.getnextday(k);
System.out.print(an.showDate());
}else if(a==2) {
k=in.nextInt();
DateUtil date2=new DateUtil(d, m, y);
if(m>12||m<0) {
System.out.print("Wrong Format");
System.exit(1);
}
if(!date2.Check()) {
System.out.print("Wrong Format");
System.exit(1);
}
DateUtil an=date2.getpreviousday(k);
System.out.print(an.showDate());
}else if(a==3) {
int y1=in.nextInt();
int m1=in.nextInt();
int d1=in.nextInt();
DateUtil frist=new DateUtil(d, m, y);
DateUtil nextt=new DateUtil(d1, m1, y1);
if(m>12||m1>12||m<1||m1<1) {
System.out.print("Wrong Format");
System.exit(1);
}
if(!(frist.Check()&&nextt.Check())) {
System.out.print("Wrong Format");
System.exit(1);
}
int g=frist.getDaysOfNexttDates(nextt);
System.out.print(g);
}else
System.out.print("Wrong Format");
}
}
参考题目7-3的要求,设计如下几个类:DateUtil、Year、Month、Day,其中年、月、日的取值范围依然为:year∈[1820,2020] ,month∈[1,12] ,day∈[1,31] , 设计类图如下:

应用程序共测试三个功能:
- 求下n天
- 求前n天
- 求两个日期相差的天数
注意:严禁使用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:值
两种日期类聚合设计的优劣比较:
聚合一里的聚合关系是层层递进的,即DateUtil中包含Day,Day中包含Month,Month中包含Year,其优点在于聚合关系清晰明了,合乎情理。缺点在于代码过于繁琐,复用性较差,给代码的编写带来了极大的困扰。
聚合二里的聚合关系则截然不同,为DateUtil中包含Day,Month,Year三个时间类,其优点在于复用性很强,可以减少Day,Month,Year类的代码书写,如此设计的缺点在于这样的书写会使其中的DateUtil类中的方法与属性繁多
解题思路:按部就班的用代码实现类图中规定的属性和方法,并通过不断的调试与分析,最终得出能通过所有测试点的代码提交即可。
二、题目集4(7-3)、题目集6(7-5、7-6)三种渐进式图形继承设计的思路与技术运用(封装、继承、多态、接口等)
编写程序,实现图形类的继承,并定义相应类对象并进行测试。
- 类Shape,无属性,有一个返回0.0的求图形面积的公有方法
public double getArea();//求图形面积 - 类Circle,继承自Shape,有一个私有实型的属性radius(半径),重写父类继承来的求面积方法,求圆的面积
- 类Rectangle,继承自Shape,有两个私有实型属性width和length,重写父类继承来的求面积方法,求矩形的面积
- 类Ball,继承自Circle,其属性从父类继承,重写父类求面积方法,求球表面积,此外,定义一求球体积的方法
public double getVolume();//求球体积 - 类Box,继承自Rectangle,除从父类继承的属性外,再定义一个属性height,重写父类继承来的求面积方法,求立方体表面积,此外,定义一求立方体体积的方法
public double getVolume();//求立方体体积 - 注意:
- 每个类均有构造方法,且构造方法内必须输出如下内容:
Constructing 类名 - 每个类属性均为私有,且必须有getter和setter方法(可用Eclipse自动生成)
- 输出的数值均保留两位小数
主方法内,主要实现四个功能(1-4): 从键盘输入1,则定义圆类,从键盘输入圆的半径后,主要输出圆的面积; 从键盘输入2,则定义矩形类,从键盘输入矩形的宽和长后,主要输出矩形的面积; 从键盘输入3,则定义球类,从键盘输入球的半径后,主要输出球的表面积和体积; 从键盘输入4,则定义立方体类,从键盘输入立方体的宽、长和高度后,主要输出立方体的表面积和体积;
假如数据输入非法(包括圆、矩形、球及立方体对象的属性不大于0和输入选择值非1-4),系统输出Wrong Format
输入格式:
共四种合法输入
- 1 圆半径
- 2 矩形宽、长
- 3 球半径
- 4 立方体宽、长、高
输出格式:
按照以上需求提示依次输出
源代码如下:
import java.util.*;
class Shape {
public Shape(){
System.out.println("Constructing Shape");
}
public double getArea()
{
return 0.0;
}
}
class Circle extends Shape{
public Circle(){
System.out.println("Constructing Circle");
}
private double radius;
public double getArea()
{
return Math.PI*getRadius()*getRadius();
}
public double getRadius() {
return radius;
}
public void setRadius(double radius) {
this.radius = radius;
}
}
class Rectangle extends Shape{
public Rectangle(){
System.out.println("Constructing Rectangle");
}
private double width;
private double lenth;
public double getArea()
{
return getWidth()*getLenth();
}
public double getLenth() {
return lenth;
}
public void setLenth(double lenth) {
this.lenth = lenth;
}
public double getWidth() {
return width;
}
public void setWidth(double width) {
this.width = width;
}
}
class Ball extends Circle{
public Ball(){
System.out.println("Constructing Ball");
}
public double getArea()
{
return Math.PI*this.getRadius()*this.getRadius()*4;
}
public double getVolume()
{
return Math.PI*this.getRadius()*this.getRadius()*this.getRadius()*4/3.0;
}
}
class Box extends Rectangle{
public Box(){
System.out.println("Constructing Box");
}
private double height;
public double getHeight() {
return height;
}
public void setHeight(double height) {
this.height = height;
}
public double getArea(){
return (super.getArea() + height * super.getWidth() + height * super.getLenth()) * 2;
}
public double getVolume()
{
return getWidth()*getLenth()*height;
}
}
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int a = in.nextInt();
switch (a)
{
case 1:
double r = in.nextDouble();
if (r <= 0){
System.out.println("Wrong Format");
System.exit(1);
}
Circle circle = new Circle();
circle.setRadius(r);
System.out.printf("Circle's area:%.2f\n", circle.getArea());
break;
case 2:
double length = in.nextDouble();
double width = in.nextDouble();
if (length <= 0 || width <= 0){
System.out.println("Wrong Format");
System.exit(1);
}
Rectangle rectangle = new Rectangle();
rectangle.setLenth(length);
rectangle.setWidth(width);
System.out.printf("Rectangle's area:%.2f\n", rectangle.getArea());
break;
case 3:
double radius = in.nextDouble();
if (radius <= 0){
System.out.println("Wrong Format");
System.exit(1);
}
Ball ball = new Ball();
ball.setRadius(radius);
System.out.printf("Ball's surface area:%.2f\n", ball.getArea());
System.out.printf("Ball's volume:%.2f\n", ball.getVolume());
break;
case 4:
double length1 = in.nextDouble();
double width1 = in.nextDouble();
double height = in.nextDouble();
if (length1 <= 0 || width1 <= 0 || height <= 0){
System.out.println("Wrong Format");
System.exit(1);
}
Box box = new Box();
box.setLenth(length1);
box.setWidth(width1);
box.setHeight(height);
System.out.printf("Box's surface area:%.2f\n", box.getArea());
System.out.printf("Box's volume:%.2f\n", box.getVolume());
break;
default :{
System.out.println("Wrong Format");
System.exit(1);
}
break;
}
}
}
掌握类的继承、多态性及其使用方法。具体需求参见作业指导书。
输入格式:
从键盘首先输入三个整型值(例如a b c),分别代表想要创建的Circle、Rectangle及Triangle对象的数量,然后根据图形数量继续输入各对象的属性值(均为实型数),数与数之间可以用一个或多个空格或回车分隔。
输出格式:
- 如果图形数量非法(小于0)或图形属性值非法(数值小于0以及三角形三边关系),则输出
Wrong Format。 - 如果输入合法,则正常输出,输出内容如下(输出格式见输入输出示例):
- 各个图形的面积;
- 所有图形的面积总和;
- 排序后的各个图形面积;
- 再次所有图形的面积总和。
编写程序,使用接口及类实现多态性,类图结构如下所示:

其中:
- GetArea为一个接口,无属性,只有一个GetArea(求面积)的抽象方法;
- Circle及Rectangle分别为圆类及矩形类,分别实现GetArea接口
- 要求:在Main类的主方法中分别定义一个圆类对象及矩形类对象(其属性值由键盘输入),使用接口的引用分别调用圆类对象及矩形类对象的求面积的方法,直接输出两个图形的面积值。(要求只保留两位小数)
输入格式:
从键盘分别输入圆的半径值及矩形的宽、长的值,用空格分开。
输出格式:
- 如果输入的圆的半径值及矩形的宽、长的值非法(≤0),则输出
Wrong Format - 如果输入合法,则分别输出圆的面积和矩形的面积值(各占一行),保留两位小数。
设计的思路与技术运用:
以上的三次题目集的编写是层层渐进的,其中前两次都是对继承的操作,虽然第二次增加了需求,但是还是围绕着继承进行的,只是需要将创建的对象储存在动态数组ArrayList中并且用类中的方法计算出结果即可。第三次的时候题目要求编写程序,使用接口及类实现多态性。在这里引入多态与接口的概念
多态:
多态是同一个行为具有多个不同表现形式或形态的能力
接口:
接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法
解题思路:
认真学习JAVA中的继承与多态,按照题目中的要求设计父子类,尽可能的使父类中包含较多的类,增加代码的复用性。而后编写子类的时候可以省不少代码书写。再次通过不断的调试与分析,最终得出能通过所有测试点的代码提交即可。
三、对三次题目集中用到的正则表达式技术的分析总结
使用Java中的字符串处理类以及正则表达式对输入字符串数据进行合法性校验及计算。(具体需求参见附件 2021-OO第04次作业-1指导书V1.0.pdf )
输入格式:
假定分水口门的数据上报时是采用人工输入的方式,每一行代表一个整点时刻的分水数据,各数据之间采用“|”符号进行分隔,每次可以输入多条数据,直到遇到用户输入“exit”为止,每一行输入数据共包含五部分:测量时间、目标水位、实际水位、开度(包含目标开度和实际开度,以“/”分隔)、流量。 各数据格式要求如下:
- 测量时间:格式为“年/月/日 时:分”,其中年份取值范围为[1,9999],“月”与“日”为一位数时之前不加“0”,日期与时间之间有一个空格,“时”与“分”之间采用冒号分隔(英文半角),“时”为一位数时之前不加“0”,“分”始终保持两位,且始终为“00”。注意:“时”数必须是24小时进制中的偶数值。
- 目标水位、实际水位、流量:均为实型数,取值范围为[1,1000), 小数点后保留1-3位小数或无小数(也无小数点)
- 目标开度、实际开度:实型数,取值范围为[1,10),必须保留2位小数,两个开度之间用“/”分隔
输出格式:
- 对输入的数据进行有效性校验,其规则如前所述,如遇到不符合规则的数据,系统应能够给出错误提示,提示规则如下:
- 如果每一行输入数据不是由“|”分隔的五部分,则输出:
Wrong Format Data:输入的数据 - 如果某一部分数据有误,则按如下方式显示:
Row:行号,Column:列号Wrong Format Data:输入的数据其中,行号为输入数的行数(从1开始),列号为6个数据的序号(从1开始,最大为6,顺序参见输入数据结构说明)
- 由于人工输入数据可能存在疏忽,在每一个输入数据两端均可能存在多余的空格,程序应该能够自动过滤这些空格(不报错)。
- 如果用户未输入数据,则直接输出Max Actual Water Level和Total Water Flow的值即可(均为0)
- 若输入无误,则对数据进行如下处理:
- 当实际开度的值大于目标开度时,程序给出如下警告:
Row:1 GateOpening Warning - 求出所输入数据中的最大实际水位值(保留2位小数),输出格式如下: Max Actual Water Level:实际水位值
- 根据每个整点时刻的瞬时流量求出所输入的所有时段的总流量(保留2位小数),其计算公式为(参见作业指导书):
$$p = \sum_{n=1}^N2*60*60*Flow$$
输出格式如下:
Total Water Flow:总流量值
输入样例1:
在这里给出一组输入。例如:
2015/8/2 4:00|133.8400|133.070|1.11/1.21|75.780
2015/8/2 6:00|133.840|133.080|11.11/1.11|72.8a0
2015/8/2 8:00|133.830|133.070|1.11/1.11|73.890
2015/8/2 10:00|133.820|133.080|1.11/1.11|74.380
exit
输出样例1:
在这里给出相应的输出。例如:
Row:1,Column:2Wrong Format
Data:2015/8/2 4:00|133.8400|133.070|1.11/1.21|75.780
Row:2,Column:4Wrong Format
Row:2,Column:6Wrong Format
Data:2015/8/2 6:00|133.840|133.080|11.11/1.11|72.8a0
输入样例2:
在这里给出一组输入。例如:
2015/8/5 2:00|133.800|133.080|1.11/1.11|73.870
2015/8/5 4:00|133.800|133.070|1.11/1.11|73.330
2015/8/5 6:00|133.830|133.110|1.11/1.21|70.610
2015/8/5 8:00|133.860|133.140|1.11/1.11|73.430
2015/8/5 10:00|133.91|133.15|1.11/1.11|73.06
2015/8/5 12:00|133.900|133.110|1.16/1.11|75.460
2015/8/5 14:00|133.920|133.140|1.16/1.11|77.030
2015/8/5 16:00|133.92|133.16|1.16/1.91|79.4
2015/8/5 18:00|133.940|133.170|1.16/1.11|76.810
2015/8/5 20:00|133.94|133.19|1.16/1.11|74.53
2015/8/5 22:00|133.930|133.200|1.16/1.11|74.400
2015/8/6 0:00|133.930|133.200|1.16/1.11|73.150
2015/8/6 2:00|133.930|133.180|1.16/1.11|74.830
2015/8/6 4:00|133.910|133.180|1.16/1.11| 73.270
exit
输出样例2:
在这里给出相应的输出。例如:
Row:3 GateOpening Warning
Row:8 GateOpening Warning
Max Actual Water Level:133.20
Total Water Flow:7510896.00
解题思路:
对于本题中给出的数据,首先我们要做的就是去除字符串中的空格。因为空格会影响正则表达式的匹配(用户输入的数据中的空格具有不确定性)。然后编写正确的正则表达式进行匹配即可。再次通过不断的调试与分析,最终得出能通过所有测试点的代码提交。
编写程序统计一个输入的Java源码中关键字(区分大小写)出现的次数。说明如下:
- Java中共有53个关键字(自行百度)
- 从键盘输入一段源码,统计这段源码中出现的关键字的数量
- 注释中出现的关键字不用统计
- 字符串中出现的关键字不用统计
- 统计出的关键字及数量按照关键字升序进行排序输出
- 未输入源码则认为输入非法
输入格式:
输入Java源码字符串,可以一行或多行,以exit行作为结束标志
输出格式:
- 当未输入源码时,程序输出
Wrong Format - 当没有统计数据时,输出为空
- 当有统计数据时,关键字按照升序排列,每行输出一个关键字及数量,格式为
数量\t关键字
输入样例:
在这里给出一组输入。例如:
//Test public method
public HashMap(int initialCapacity) {
this(initialCapacity, DEFAULT_LOAD_FACTOR);
}
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +
initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY;
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " +
loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}
exit
输出样例:
在这里给出相应的输出。例如:
1 float
3 if
2 int
2 new
2 public
3 this
2 throw
解题思路:
本题的要求是:写一个类似于word文档中查找的功能,对java源码中的关键字进行查找,并且统计个数。分析题目可知,我们首先要做的是将注释中的字符串去除①,还要将“”内的字符串去除②,之后才能进行字符串的匹配,再次通过不断的调试与分析,最终得出能通过所有测试点的代码提交。
①if(str.matches("(.*)//(.*)"))
{String a[]=str.split("//");
ss.append(a[0]+" ");
}
② Pattern p=Pattern.compile("\"(.*?)\"");
Matcher m=p.matcher(s);
while(m.find()==true){
s=s.replace(m.group()," ");
m=p.matcher(s);
}
四、题目集5(7-4)中Java集合框架应用的分析总结
集合框架是为表示和操作集合而规定的一种统一的标准的体系结构。任何集合框架都包含三大块内容:对外的接口、接口的实现和对集合运算的算法
编写程序统计一个输入的Java源码中关键字(区分大小写)出现的次数。说明如下:
- Java中共有53个关键字(自行百度)
- 从键盘输入一段源码,统计这段源码中出现的关键字的数量
- 注释中出现的关键字不用统计
- 字符串中出现的关键字不用统计
- 统计出的关键字及数量按照关键字升序进行排序输出
- 未输入源码则认为输入非法
输入格式:
输入Java源码字符串,可以一行或多行,以exit行作为结束标志
输出格式:
- 当未输入源码时,程序输出
Wrong Format - 当没有统计数据时,输出为空
- 当有统计数据时,关键字按照升序排列,每行输出一个关键字及数量,格式为
数量\t关键字
import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Set; import java.util.TreeMap; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Main { public static void main(String[] args) { String keyWord[]=Keywords(); String a; StringBuilder sb = new StringBuilder(); Scanner in=new Scanner(System.in); int i,j,l,cnt=0; TreeMap<String,Integer> map=new TreeMap<String, Integer>(); // Map<String,Integer> map=new HashMap<String, Integer>(); for(i=0;i<keyWord.length;i++) map.put(keyWord[i], 0); a=in.nextLine(); while(!a.equals("exit")) { if(a.matches("(.*)//(.*)")) { String[]b=a.split("//"); sb.append(b[0]+" "); } else sb.append(a+" "); a=in.nextLine(); } String str=sb.toString(); // String regex="[(\"(.*?)\")|(/\\*(.*?)\\*/)]"; Pattern p=Pattern.compile("\"(.*?)\""); Matcher m=p.matcher(str); while(m.find()) str=str.replace(m.group()," "); p=Pattern.compile("/\\*(.*?)\\*/"); m=p.matcher(str); while(m.find()) str=str.replace(m.group()," "); if(str.isEmpty()) { System.out.println("Wrong Format"); System.exit(0); } str = str.replace("[", " "); str = str.replace("]", " "); str = str.replace("-", "a"); str = str.replace("*", "a"); str = str.replace("/", "a"); str = str.replace("+", "a"); str = str.replace(">", "a"); str = str.replace("=", "a"); str = str.replace("!", "a"); str = str.replace(":", "a"); str = str.replace("\\", "a"); // s = s.replace("[|]", " "); // str = str.replaceAll("<|>|-|\\+|\\*|>|=|!|:|\\\\", " "); str=str.replaceAll("\\p{P}+", " "); str= str.replaceAll("[^a-zA-Z]", " "); // str=str.replace("[\\[\\]]"," "); // str=str.replace("[,-\\*/+>=!:]"," "); String s1[]=str.split(" +"); for(i=0;i<keyWord.length;i++) { for(j=0;j<s1.length;j++) { if(s1[j].equals(keyWord[i])) { cnt++; } } map.put(keyWord[i], cnt); cnt=0; } // Set set=map.keySet(); // Object[] arr=set.toArray(); // Arrays.sort(arr); // for(Object k:arr) // if(map.get(k)!=0) // System.out.println(map.get(k)+"\t"+k); for(i=0;i<53;i++) if(map.get(keyWord[i])!=0) System.out.println(map.get(keyWord[i])+"\t"+keyWord[i]); } public static String[] Keywords(){ String[] keywordString = {"abstract", "assert", "boolean", "break", "byte", "case", "catch", "char", "class", "const", "continue", "default", "do", "double", "else", "enum", "extends", "for", "final", "finally", "float", "goto", "if", "implements", "import", "instanceof", "int", "interface", "long", "native", "new", "package", "private", "protected", "public", "return", "short", "static", "strictfp", "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try", "void", "volatile", "while", "true", "false", "null"}; return keywordString; } }
本题运用的集合框架为Map和Set,用来储存关键字和其出现的次数。Set继承于Collection接口,是一个不允许出现重复元素,并且无序的集合,主要有HashSet和TreeSet两大实现类。Map 接口中键和值一一映射. 可以通过键来表示对应关键字出现的次数,通过值来表示对应的关键字。调用set是为了对筛选出的关键字进行排序。
l 采坑心得
写代码从来都不是一蹴而就的,bug与异常肯定再所难免,在此我来谈谈本次习题除了语法错误还有关于yi'shi的细节之外导致我没有通过测试点的大几个“坑”
- java中正则表达式比较有意思,这里列举几个常见的坑
1.[]符号,中括号表示其中的数据都是或的关系
如果[\\w+]是匹配条件 abc是否可以匹配的到呢?
首先\\w表示a-z A-Z 0-9 _多个字符组合,显然abc任意一个在里面的,后又有+号,表示有多个字符,所以abc可以匹配\\w+
但是[\\w+]表示的意思是多个字符的或,注意是或,所以[\\w+]等同于[a-z|A-Z|0-9|_],这里面的或只有单个字符
所以a或者b或者c都可以匹配[\\w+],但是abc不可以,如何让abc可以匹配呢很简单只需要将条件外面加上+号标识多个字符就可以了。
2.转义符号
java转义很麻烦各种\\,需要仔细理解
java中\是没有意义的,在字符串中你出现一个\,编译器会告诉你是错误的,不能这样
所以java中\\表示一个\。在正则表达式匹配中如匹配数字写的是\\d其实是\\表示一个\最后的效果是\d.
这个时候有人要问了,我只要匹配\d这个字符而不是匹配数字怎么办,这个时候需要在加一个转义符,告诉大家这个字符不是\d表示的数字,而是具体字符串\d,具体的结果是很蛋疼的在加上一个转义字符\\,所以会出现\\\\d,java会解析成\\d,表示对\d在做转义,就是单纯的\d。
再比如[\\]这个简单的表达式,如果你去调用则会报错,为什么?
因为java会认为你只传了一个转义符,而单独的转义符是没有意义的,如果你要匹配\号,需要的表达式是\\\\前面的\\表示转义符号,后面的\\表示真正匹配的\号。- -!
java转义字符关键是两个\\表示一个\,会让人费解,需要注意。
2. 子类继承父类的一个小坑
子类继承父类,如果重写了父类构造方法中包含的方法(如此次测试的init()方法),一定要注意,在子类中声明的变量(如此次测试的sonStr1和sonStr2),即使初始化了,仍会是默认值(此次为String类型,所以为null),如果子类声明的变量是和父类同名的变量,会以父类的为准。子类继承父类,只有重写方法有效,重写属性无效。在子类声明的变量,无论是否初始化了,在父类的构造方法阶段,都不能拿到子类声明的变量属性,只有执行到子类的构造方法,才能得到。因此一定要注意是不是做好空处理,不然就是崩溃问题
3. java多态注意事项
1、private 修饰的方法没有多态特性
public class PrivateOverride { /** * dddd */ private void f(){ System.out.println("private f()"); } public static void main(String[] args) { PrivateOverride po = new Derived(); po.f(); } } class Derived extends PrivateOverride { /** * eeee */ public void f(){ System.out.println("public f()"); } }
输入结果为private f()
因为父类的方法都是私有的,自然不会被子类重写,而呈现多态特性。
2、成员变量不呈现多态特性,只针对方法(非静态,非私有)
class Super{ public int field = 0; public int getField(){ return field; } } class Sub extends Super{ public int field = 1; public int getField(){ return field; } public int getSuperField(){ return super.field; } } public class FieldAccess { public static void main(String[] args) { Super sup = new Sub(); System.out.println("sup.field = " + sup.field + ", sup.getField() = " + sup.getField()); Sub sub = new Sub(); System.out.println("sub.field = " + sub.field + ", sub.getField() = " + sub.getField() + ", sub.getSuperField() = " + sub.getSuperField()); } }
输入结果:
sup.field = 0, sup.getField() = 1
sub.field = 1, sub.getField() = 1, sub.getSuperField() = 0
3、静态方法也不呈现多态特性
class StaticSuper{ public static String staticGet(){ return "Base staticGet()"; } public String dynamicGet(){ return "Base dynamicGet()"; } } class StaticSub extends StaticSuper{ public static String staticGet(){ return "Derived staticGet()"; } public String dynamicGet(){ return "Derived dynamicGet()"; } } public class StaticPolymorphism { public static void main(String[] args) { StaticSuper sup = new StaticSub(); System.out.println(sup.staticGet()); System.out.println(sup.dynamicGet()); } }
输入结果:
Base staticGet()
Derived dynamicGet()
l 改进建议
丰富测试样例,使其具有普遍性,以便保证最终提交的程序没有bug
l 总结
通过以上的编程练习,进行了对JAVA语言的进一步学习。众所周知面向对象编程的三大核心是:封装,继承,多态。现在我对这三者都有所了解和实践,对其有一定的掌握,在面向对象的程序设计上又有了新的进步。所谓继承,即子类与父类,继承的设计可以极大的提高我们代码的复用性。而多态提高了代码的维护性(继承保证);提高了代码的扩展性。为以后职业生涯的发展奠定了基础,在成为顶级软件工程师的道路上又迈出了坚实的一步,以后的道路能少走弯路,希望以后的学习生涯能顺风顺水。

浙公网安备 33010602011771号