oo总结2

oo总结2


前言:

     经过一段时间的学习逐渐对java有一个深入的了解,在近一段时间对java中面向对象的继承,方法重写/载,多态,抽象类,接口等等有一个基础了解,以及掌握了它们的基本用法。近几期作业题目难度较为简单,题目量也有所减少。但是在学习时仍然发现对正则表达式的不熟练等等情况。

 


 

设计与分析:

    1、题目集4和题目集5中的7-2和7-5中要求自己设计日期类(不允许使用java的日期类),要求实现

求下n天
求前n天
求两个日期相差的天数

  其中题目集4中的相关类图如下

                                                         

 

    题目设计了四个类对应的几个聚合关系,DateUtill中含有Day的一个对象而Day类有含有Month的对象,Month含有Year的对象。

    通过各个类的自增加/自减少来达到题目的求下n天的要求。

    而求两个日期相差的天数可以用两日期中较大日期不断减一天,即不断求下一天操作,知道两个日期相等时所求的n个下一天便是相邻天数。

而题目集5中的聚合关系如图所示:

                                                          

 

可见在Day,Month,Year聚合在DateUtill中。个人觉得这样设计使得DateUtill对象中直接含有Day,Month,与Year的对象,在使用时可直接通过一个DateUtill对象调用其中3个变量的方法,可以避免像第一种聚合关系在访问Year对象时的繁琐,如:

dateutill.day.getMonth().getYear().getValue() 

 并且在直观上类与类的关系更加清晰。

 2、题目集4(7-3)、题目集6(7-5、7-6)中要求了三种渐进式图形继承设计。

                                                    

有shape作为一个不确定图形的父类,它含有一个空构造方法,以及一个返回0.0的getArea方法。子类矩形,圆形等继承父类Shape定义了具体的图形形状,并且各自拥有独特的属性以及不同的求面积方法。其实在后面的学习中我了解到像Shape类这样的抽象的,不定性的,无具体实例的,没有包含足够的信息来描绘一个具体的对象,可以设置为抽象类。在子类中将其具像化。

   其中题目集6定义了一个getArea的接口用来求面积

                                                                                                          

  接口定义一系列行为,可用"has-a"关系进行建模,s(圆形)Re(矩形)实现了改接口,即这两个对象均有求面积这个行为,由于接口中的方法为抽象的,这个方法在两个类中被重写从而被实现。

3、题目集5   7-4 统计Java程序中关键词的出现次数

 

编写程序统计一个输入的Java源码中关键字(区分大小写)出现的次数。说明如下:

  • Java中共有53个关键字(自行百度)
  • 从键盘输入一段源码,统计这段源码中出现的关键字的数量
  • 注释中出现的关键字不用统计
  • 字符串中出现的关键字不用统计
  • 统计出的关键字及数量按照关键字升序进行排序输出
  • 未输入源码则认为输入非法

输入格式:

输入Java源码字符串,可以一行或多行,以exit行作为结束标志

输出格式

  • 当未输入源码时,程序输出Wrong Format
  • 当没有统计数据时,输出为空
  • 当有统计数据时,关键字按照升序排列,每行输出一个关键字及数量,格式为数量\t关键字

我的源码如下

  1 import java.util.Arrays;
  2 import java.util.HashMap;
  3 import java.util.Iterator;
  4 import java.util.Map;
  5 import java.util.Scanner;
  6 import java.util.Set;
  7 
  8 public class Main {
  9 
 10     public static void main(String[] args) {
 11         // TODO 自动生成的方法存根
 12         int count=0;
 13         Scanner sc=new Scanner(System.in);
 14         String a;
 15         StringBuilder ss=new StringBuilder();
 16         Map<String, Integer> map=new HashMap<String, Integer>();
 17         String []key= { "abstract","assert","boolean","break","byte","case","catch",
 18                  "char","class","const","continue","default","do","double","else",
 19                  "enum","extends","false","final","finally","float",
 20                  "for","goto","if","implements","import","instanceof",
 21                  "int","interface","long","native","new","null","package",
 22                  "private","protected","public","return","short","static",
 23                  "strictfp","super","switch","synchronized","this","throw",
 24                  "throws","transient","true","try","void","volatile","while"
 25             
 26         };
 27  boolean flag=true;
 28  while(flag) {
 29      a=sc.nextLine();
 30      if (a.equals("exit")) {
 31          StringBuilder codeStr = new StringBuilder(ss.toString().replaceAll("\\s+", ""));
 32          if (codeStr.length() == 0) {
 33              flag = false;
 34              System.out.println("Wrong Format");
 35          }
 36          break;
 37      }
 38      if(a.matches("(.*)//(.*)")) {
 39          String t[]=a.split("//");
 40          ss.append(t[0]+" ");
 41      }else {
 42          ss.append(a+" ");
 43      }
 44  }
 45         
 46  ss = new StringBuilder(ss.toString().replaceAll("/\\*(.*)\\*/", " "));
 47  ss=new StringBuilder(ss.toString().replaceAll("\"(.*?)\""," "));
 48  String s=ss.toString();
 49  
 50  s=s.replace("]"," ");
 51  
 52  s=s.replace("["," ");
 53 s=s.replace("-"," ");
 54  s=s.replace("*"," ");
 55  s=s.replace("/"," ");
 56  s=s.replace("+"," ");
 57  s=s.replace(",", " ");
 58 s=s.replace(">"," ");
 59 s=s.replace("<", " ");
 60  s=s.replace("="," ");
 61 s=s.replace("!"," ");
 62  s=s.replace(":"," ");
 63 s=s.replace("\\"," ");
 64 s=s.replace("("," ");
 65         s=s.replace(",", " ");
 66         s=s.replace("【", " ");
 67 s=s.replace("】", " ");
 68 s=s.replace(")", " ");
 69 s=s.replace("{", " ");
 70 s=s.replace("}", " ");
 71 s=s.replace(".", " ");
 72 s=s.replace(";", " ");
 73  s= s.replace("[^a-zA-Z]", " ");
 74  String []st=s.split("\\s+");
 75  /*for(int m=0;m<st.length;m++) {
 76      System.out.println(st[m]);
 77  }*/
 78         for(int i=0;i<st.length;i++) {
 79             for(int j=0;j<key.length;j++) {
 80                 
 81                 if(st[i].equals(key[j])) {
 82                     map.put(key[j], 0);
 83                 }
 84                 
 85             }
 86         }
 87         for(int k=0;k<st.length;k++) {
 88             for(int l=0;l<key.length;l++) {
 89                 if(st[k].equals(key[l])) {
 90                     count=map.get(key[l]);
 91                     map.put(key[l],count+1);
 92                     
 93                 }    
 94             }
 95         }
 96         Set set=map.keySet();//
 97         Object[] arr=set.toArray();
 98         Arrays.sort(arr);
 99         for(Object k:arr){
100             System.out.println(map.get(k)+"\t"+k);
101         }
102 
103         
104     }
105 
106 }

 

  主要思路为对输入进的每一行字符串利用正则表达式进行去注释包含,后对每一个“ ”中的字符串进行去除,最后用空格替换可能链接两个关键字的特殊符号如‘+’等等,以空格分隔得到各个字字符串的数组后与java中的关键字进行匹配即可。

  其中利用到了Map接口,Map接口存储一组键值对象,提供key(键)到value(值)的映射。程序中:

Map<String, Integer> map=new HashMap<String, Integer>();

  定义了一个从String到integer的映射关系,每一个String(在此为出现的关键字)对应了一个整数int(在此为出现次数),单输入的字符串中匹配到关键字时就将改关键字加入到map的key中,后按匹配到几次给key对应的value赋值。

        而Set集合类似于一个罐子,程序可以依次把多个对象“丢进”Set集合,而Set集合通常无添加顺序,通过keyset()方法返回该集合中的所有key对象形成的set集合后排序输出各个key对应的value即可。

 



 

踩坑心得:

  在上述题目集5的7-5中Pta有如下测试点

   1、前n天:整型数最大值测试

   2、下n天:整型数最大值测试 

   当我还是以上次题目4中的解决方法

public DateUtil getpreviousday(int n) {
		day.leapYear();
		for(int i=1;i<=n;i++) {
			day.dayReduce();
			if(!day.validate()) {
				day.getMonth().monthReduction();
				
				day.resetMax();
			}
			if(!day.getMonth().validate()) {
				day.getMonth().resetMax();
				day.getMonth().getYear().yeardecrease();
				day.leapYear();
			}
			
		} 

   以一个不断的循环求下一天,本来一看似乎问题不大可pta却显示

                                           

 可见但求n天时由于数值过大运行时间过长而过不了点,此时就需要将方法重写为一个效率更高的方法。可以先判断过了多少个年或月将这些先相加从而不再一天天的循环从而提高程序的运行效率。

不过这也提醒了我,编程之时虽然程序是电脑执行,当我们写好程序之后电脑跑,这个电脑执行的过程的快慢与麻烦好像与我们的麻烦无关,但是我们在编写一个程序时应该尽量以一种简洁又有效的方式来完成我们的目的,而不是不管代码运行时间或是有多复杂,以一种能抓到老鼠的猫就是好猫的心态来编写我们的代码。


 

 

总结

    近一段时间的学习java中面向对象的继承,方法重写/载,多态,抽象类,接口等等在此作一个总结。

1、继承

继承是java面向对象编程技术的一块基石。继承使得子类可以继承父类的属性与方法,即子类对象可以通过继承来获得父类的特征与行为。

1 //类的继承格式
2 class 父类 {
3 }
4  
5 class 子类 extends 父类 {
6 }

 

当我们设计一系列类时,我们可以将这一系列中的相同的属性,行为提取出来设为一个父类或是一个抽象类,这样可以提高代码的复用性(复用性主要是可以多次使用,不用再多次写同样的代码)。

此外还应注意继承中的几个点:

  java中的类继承只支持单一继承,即一个子类只能有一个直接父类。

  父类的private数据子类的的确确继承了下来,但是由于访问性的限制不能直接在子类中访问他们。如果父类定义了公共的修改器/访问器,则可以通过他们进行访问。

  继承以“is-a”关系建模,不要为了重用方法而盲目继承一个类,如person类继承出tree类是毫无意义的。

  访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为 public,那么在子类中重写该方法就不能声明为 protected 

  声明为 final 的方法不能被重写。

  声明为 static 的方法不能被重写,但是能够在子类被再次声明,则父类中的相应方法被隐藏,可通过父类名.方法   调用父类中的该方法。

  当需要在子类中调用父类的被重写方法时,要使用 super 关键字。

   

       构造方法链: 在任何情况下,构造一个类的实例时,将会调用沿着继承链的所有父类的构造方法。当构造一个子类的对象时,子类的构造方法会在完成自己的任务之前,首先调用它的父类的构造方法。如果父类继承自其他类,那么父类的构造方法又会在完成自己的任务之前,调用它自己的父类的构造方法。这个过程持续到沿着这个继承层次结构的最后一个构造方法被调用为止。在我的理解中首先调用父类的构造方法是先对父类中的属性进行初始化,然后子类对其自身的新加的属性进行初始化从而完成对一个对象的初始化。

  注意如果子类的构造方法中没有显式调用父类的构造方法(无参或有参),则编译器会自动将super()作为构造方法的第一条语句。如果父类中刚好没有一个无参的构造方法则程序不能正常编译。因此如果要设计一个可以被继承的类,最好提供一个无参的构造方法以避免程序出错。

 

2、多态

  书上解释道使用父类对象的地方都可以使用之类的对象便是通常所说的多态。简单来说多态可以解释为父类便量可以引用子类型的对象。 多态可以提高程序的灵活性与简洁性。

多态存在的条件:

  继承

  子类重写父类方法

  父类引用指向子类对象:object p = new student();

 举个栗子

 

 1 package routing;
 2 
 3 public class Test {
 4     public static void main(String[] args) {
 5       show(new Cat());  
 6       show(new Dog());  
 7                 
 8       Animal a = new Cat();  // 向上转型  
 9       a.eat();              
10       Cat c = (Cat)a;        // 向下转型  
11       c.work();        
12   }  
13             
14     public static void show(Animal a)  {
15       a.eat();  
16         // 类型判断
17            if (a instanceof Cat)  { 
18             Cat c = (Cat)a;  
19             c.work();  
20         } else if (a instanceof Dog) { 
21               Dog c = (Dog)a;  
22                 c.dowhat();  
23         }  
24     }  
25 }
26  
27  class Animal {  
28     public void eat();  
29 }  
30   
31 class Cat extends Animal {  
32     public void eat() {  
33         System.out.println("吃鱼");  
34     }  
35     public void work() {  
36         System.out.println("抓老鼠");  
37     }  
38 }  
39   
40 class Dog extends Animal {  
41     public void eat() {  
42         System.out.println("吃肉");  
43     }  
44     public void dowhat() {  
45         System.out.println("看家护院");  
46     }  
47 }
48 /*输出为
吃鱼
49 抓老鼠 50 吃肉 51 看家护院 52 吃鱼 53 抓老鼠*/

 

  先了解书上的动态绑定:一个变量可以有声明类型和实一个变量必须被声明为某种类型。变量的这个类型称为它的声明类型。这里,a的声明类型是Animal。变量的实际类型是被变量引用的对象的实际类即有new出什么东西来决定。这里,a的实际类型是Cat,因为a引用硬用new Cat()创建的对象。a调用哪个toString方法由a的实际类型决定。这称为动态绑定。

所以在第9行中调用a.eat()出现了吃鱼而不是无输出。

  当使用多态方式调用方法时,首先检查父类中是否有该方法,如果没有,则编译错误;如果有,再去调用子类的同名方法。故利用多态时需保证父类与子类都有该方法。

 

在第18行,cat c=(cat)a;明明a就是一个对cat对象的引用,为什么还要特地声明呢?

  总有  object o=new student();  因为子类的实列也是一个父类的实列,所以这语句是合法的,这称为隐式转化。总是可以将一个子类实列转换为一个父类的变量,称为向上转换。

  但是为什么cat c=a;却是错误的语句呢?

  我们注意到cat对象总是他的父类animal的一个实列。而父类对象不一定是 子类 的实例。即使可以看到a实际上是一个 Cat 的对象,但是编译器还没有聪明到知道这一点。为了否诉编译器 a 就是一个Cat 对象,就要使用显式转换。

cat c=(cat)a;

 

   总是可以将一个子类的实例转换为一个父类的变量,称为向上转换,因为子类的实例总是它的父类的实例。当把一个父类的实例转换为它的子类变量 ,称为向下转换时,必须使用转换标记(子类名)”进行显式转痪。向编译器表明你的意图。为使转换成功,必须确保要转换的对象是子类的一个实例,

即该父类变量引用了一个子类对象。

如果父类对象不是子类的一个实例,就会出现运行时异常。因此,一个好的做法,在尝试特换之前确保该该对象是另一个对象的实例,这可以利用操作符 instanceof来实现。

 

后来上网查阅资料了解到多态的多种实现方式:

多态的实现方式

  方式一:重写:

  方式二:接口

  方式三:抽象类和抽象方法

 

3、抽象类

  在类的继承层次结构中,每个子类变得更加明确,而具体如果从子类不断向父类追溯,类就会变得更加通用,更加不明确,父类应包含子类拥有的共同特征,但有的时候一个父类很抽象,没有办法来实例化对象,或者说他没有对象,没有任何具体的实例,不能用来描绘对象,如果一个类中没有足够的信息来描绘一个具体的对象,这样的类称为抽象类。

 

  抽象类除了不能实例化对象之外,类的其它功能依然存在,成员变量、成员方法和构造方法的访问方式和普通类一样。

  由于抽象类不能实例化对象,所以抽象类必须被继承,才能被使用

 

如果你想设计这样一个类,该类包含一个特别的成员方法,该方法的具体实现由它的子类确定,那么你可以在父类中声明该方法为抽象方法。abstract 关键字同样可以用来声明抽象方法,抽象方法只包含一个方法名,而没有方法体。

注意到:

  如果一个类包含抽象方法,那么该类必须是抽象类。

   任何子类必须重写父类的抽象方法,或者声明自身为抽象类。

   抽象类中不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

   static方法不能为抽象的。  

  尽管不能创造一个抽象类的实列,但抽象类任可作为一个数据类型。

 

4、接口   (  interface

  接口是一个与类类似的结构,用于对对象定义共同的操作。

  接口在许多方面与抽象类相同,但接口是对相关一类对象拥有的共同行为进行抽象,

 在java中接口被看做特殊的类,与抽象类类似,接口不能new出一个实例对象。一个类可以实现接口(implements),即该类的对象拥有接口所定义的行为,

 类与接口的关系称为接口继承,简称继承。接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类

public interface text{
    public static final int k=1;
    public void p();
    
}
//接口中的数据域都是public static final的所有方法都是public abstract的
//java允许忽略上述修饰符,下面的接口与上等价
public interface T{
    int k=1;
            void p();
}

 

  在java8中引入了关键字default的一个默认接口方法,实现该接口的类可以使用该方法的默认实现。还允许了接口中存在公有的静态方法,接口中的静态方法与类中的一样使用。

  一个类只能继承一个父类,而一个类却可以实现多个接口。

...implements 接口名称, 其他接口名称, 其他接口名称..., ...{
}

  一个接口能继承另一个接口,和类之间的继承方式比较相似。接口的继承使用extends关键字,子接口继承父接口的方法。但接口可以多继承:

public interface 接口 extends 其他接口, 其他接口2.....{}

 

   标记接口是没有任何方法和属性的接口.它用来表明一个类拥有某些希望拥有的特征。

 


 

 

  经过一段时间的学习,更加深入了解java的特征,总的来说java的学习过程还是比较有趣的。

  

  如文章有错误之处请指出。

 

 

 

 

posted @ 2021-05-01 21:18  小小菜鸟、  阅读(63)  评论(0)    收藏  举报