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("http://");
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("http://"); 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號