5.5 抽象类

一、为什么要使用抽象类、抽象方法

  当编写一个类时,常常会定义一些类用于描述该类的行为方式,那么这些方法都有具体的方法体。但在某些情况下,某个父类只知道其子类应该包含什么样的方法,但无法准确知道这些子类的如何实现这些方法。

  例如:定义了一个Shape类,这个类应该提供一个计算周长的方法calPerimeter(),但不同的Shape子类对计算周长的方法是不一样的,即Shape类无法准确知道不同的子类计算周长的方法。但是如果不在Shape类中声明calPerimeter()方法,我们定义了Shape类型的三角形和矩形实例(都是从Shape类的子类派生出来的,且在子类中有定义calPerimeter()方法),这时我们就无法调用calPerimeter()方法,因为Shape类的实例不含calPerimeter()方法。如果必须调用calPerimeter()方法,我们就必须将两个实例强制转换为三角形和矩形类型。

  如何既让Shape类里包含calPerimeter()方法,有无须提供其方法的实现呢?使用抽象类即可满足该要求:抽象方法只有方法签名,没有实现的方法。

二、抽象方法和抽象类

抽象类和抽象方法必须使用abstract修饰符来定义,有抽象方法的类只能被定义成抽象类,抽象类里可以没有抽象方法。

抽象方法和抽象类的规则:

1、都必须必须由abstract修饰,抽象方法不能有方法体。

2、抽象类不能被实例化。即使抽象类里不包含抽象方法,这个抽象方法也不能创建实例。

3、抽象类可以包含成员变量、方法(普通方法和抽象方法都可以)、构造器、初始化块、内部类(接口、枚举)5种成分。抽象类主要用于派生子类。

4、含有抽象方法的类只能被定义成抽象类。

抽象类可以用“有得有失”来形容,得——抽象类获得了抽象方法;失——抽象类不能创建实例,只能派生子类。

 1 abstract class Shape 
 2 {
 3     {System.out.println("执行Shape的初始化块");}
 4     private String color;
 5     //定义一个计算周长的抽象方法
 6     public abstract double calPerimeter();
 7     //定义一个返回形状的方法
 8     public abstract String getType();
 9     //定义Shape构造器,该构造器并不是用于创建Shape对象的
10     //而是用于被子类调用
11     public Shape(){}
12     public Shape(String color)
13     {
14         System.out.println("执行Shape的构造器...");
15         this.color=color;
16     }
17     
18     //color的setter()和getter()方法
19     public void setColor(String color)
20     {
21         this.color=color;
22     }
23     public String getColor()
24     {
25         return this.color;
26     }
27 }
View Code

  上面的Shape类里的包含了两个初始化方法:calPerimeter()和getType(),所以这个类只能被定义成抽象类。Shape类既包含初始化块,也包含构造器,这些都不是在创建Shape对象时调用,而是在创建其子类的实例时被调用。

  下面定义一个三角形类,三角形被定义成普通类,因此必须实现Shape类里的所有方法。该类可以创建实例

 1 public class Triangle extends Shape 
 2 {
 3     //定义三角形的三边
 4     private double a;
 5     private double b;
 6     private double c;
 7     public Triangle(String color,double a,double b,double c)
 8     {
 9         super(color);
10         this.setSides(a,b,c);
11     }
12 
13     public void setSides(double a,double b,double c)
14     {
15         if(a>=b+c||b>=a+c||c>a+b)
16         {
17             System.out.println("三角形两边之和必须大于第三边");
18             return;
19         }
20         else
21         {
22             this.a=a;
23             this.b=b;
24             this.c=c;
25         }
26     }
27 
28     //重写父类的计算周长的抽象方法
29     @Override
30     public double calPerimeter()
31     {
32         return a+b+c;
33     }
34     //重写父类中的返回形状的抽象方法
35     public String getType()
36     {
37         return "三角形";
38     }
39 }
View Code

   下面再定义一个Circle普通类,Circle类也是Shape类的一个子类

 1 class Circle extends Shape 
 2 {
 3     private double radius;
 4     public Circle(String color,double radius)
 5     {
 6         super(color);
 7         this.radius=radius;
 8     }
 9 
10     public void setRadius(double radius)
11     {
12         this.radius=radius;
13     }
14     //重写Shape类计算周长的抽象方法
15     @Override
16     public double calPerimeter()
17     {
18         return 2*Math.PI*radius;
19     }
20     @Override
21     //重写父类的返回形状的方法
22     public String getType()
23     {
24         return getColor()+"圆形";
25     }
26 }
View Code

测试上面的类:

 1 class ShapeTest 
 2 {
 3     public static void main(String[] args) 
 4     {
 5         //我们不能创建Shape的实例
 6         //Shape s=new Shape("黄色");//ShapeTest.java:6: 错误: Shape是抽象的; 无法实例化
 7         Shape s1=new Triangle("黑色",3,4,5);
 8         Shape s2=new Circle("蓝色",3);
 9         System.out.println(s1.getType());//三角形
10         System.out.println(s1.calPerimeter());//12.0
11 
12         System.out.println(s2.getType());//矩形
13         System.out.println(s2.calPerimeter());//18.84955592153876
14     }
15 }
16 ---------- 运行Java捕获输出窗 ----------
17 执行Shape的初始化块
18 执行Shape的构造器...
19 执行Shape的初始化块
20 执行Shape的构造器...
21 三角形
22 12.0
23 蓝色圆形
24 18.84955592153876
25 
26 输出完成 (耗时 0 秒) - 正常终止
View Code

上面的main()方法定了两个Shape类型的引用变量,分别指向Triangle对象和Circle对象。由于在Shape类中定义了calPerimeter()方法和getType()方法,所以程序可以直接调用s1变量和s2变量的calPerimeter()和getType()方法,无需强制转换为其子类类型。

  利用抽象类和抽象方法的优势,可以很好地发挥多态的优势。当使用abstract修饰类时,表面这个类只能被继承;当使用abstract修饰方法时,表明这个方法必须有子类提供实现(即重写)。而final修饰的类不能被继承,final修饰的方法不能被重写。因此final和abstract永远不能同时使用。 

 注意:

(1)abstract只能修饰类、方法,不能修饰成员变量、构造器、初始化块。

(2)static修饰一个方法时,表明这个方法属于类本身,即通过该类就可以调用该类本身。但如果该方法被定义成抽象方法,则将导致通过类来调用方法时出现错误(调用一个没有方法体的方法肯定会引起错误)。因此static和abstract不能同时修饰一个方法,即没有所谓的类抽象方法。

(3)static和abstract并不是绝对互斥的,static虽然不能同时修饰某个方法,但他们可以同时修饰内部类。

(4)abstract关键字修饰的方法必须被子类重写才有意义,否则该方法将永运不会有方法体。因此abstract方法不能定义为private访问权限,即private和abstract不能同时修饰方法。

 三、抽象类的作用

  抽象类不能创建实例,只能当成父类来继承。从语法角度来讲,抽象类是从多个具体的类中抽象出来的父类,它具有更高层次的抽象。从多个具有相同特征的类中抽象出一个抽象类,以这个抽象类作为其子类的模板,从而避免了子类设计的随意性。

  抽象类体现的就是一种模板模式的设计,抽象类作为多个子类的通用模板,子类在抽象类的基础上进行扩展、改造,但子类总体上会大致保留抽象类的行为方式。

  如果编写一个抽象父类,父类提供多个子类的通用方法,并把一个或多个方法留给其子类来实现,这就是一种模板模式。

posted @ 2020-03-04 20:10  小新和风间  阅读(337)  评论(0编辑  收藏  举报